Thanks a ton, Chuck!
This begins the work of providing some insulation and elasticity between the DevForce persistence machinery and consumers such as the UI. That is an essential step and I'm pleased to see you take up that cause.
Some important next steps:
- Base on an interface (essential for testability)
- Virtual members
- Use dependency inversion (consider IoC); should not "new" an EntityManager ;
- I would never use EntityManager.DefaultManager in production code; this is a demo feature. Avoid static classes and methods when possible ... and with EM, it's always possible. See also remarks at the bottom of this post.
- I typically configure an EM outside the repository and inject it; sometimes I further configure it inside the repository (e.g., for Saving/Saved event handling).
- I distinguish by verb choice whether the repository will look only in cache (Find) or potentially go to the server (Get); in your example, you defined a Get that DevForce would have called a Find.
I don't mind if you develop a different vocabulary; I only ask that you be clear on the difference and be consistent.
[Hmm. Now that I re-read your code, I see that you reverse the standard DevForce meanings of Find and Get. I'd advise against that because people who become familiar with DevForce terminology may be confused as I was.]
- GetById<T> method would be a typical method in a generic repository.
- I don't think a Repository should have a Create method. Repositories don't know how entities are created. In your example, Create insulates the caller from direct contact with the EM (good!) but that's it. Better to have Add (for new) and Attach (for existing) methods for associating entities with EMs.
- A Repository for Silverlight clients would use async calls and requires a different API. I haven't settled on a good set of practices for this myself. Still feeling my way.
I know that many repository authors favor these general purpose repositories. They often pin them to particular "Aggregates" by defining a Repository<T> where T is an aggregate.
I've found that while this may work well for the short-lived repositories of web applications, it isn't so great for RIA and smart client apps.
I prefer Repositories built specifically for application "modules" ... for the larger, coherent workflows that we tend to put in separate modules such as "Order Entry", "Inventory", "Logistics", "Accounting".
When I build these out, I give the repositories fully semantic "query" methods that are specific in purpose and clear in intent: GetCustomers(String), GetCustomer(Guid), GetOrdersForCustomer(Customer).
Yes, these are not composable (they return entities). Sometimes you need to return a query object that the caller can elaborate. Not afraid of those at all. But if the use cases call for static query, I'd tend to encapsulate that static query in a method to reduce the need for (risk of) the caller getting the query right.
Your FindAll, which takes an expression, is a useful hybrid; it enables flexibility in the predicate while taking over the other aspects of the query.
I do not fear that the repository API will become to large. If it starts heading in that direction, I treat that as a code smell. Maybe the module is focused enough. Maybe it hosts multiple workflows that should be divided among multiple repositories.
Btw, two repositories may share the same EntityManager instance ... but you should know why they do and be aware of the consequences. Calling "Save" will affect all of the repositories that use that EM. Make sure you appreciate the implications.
Please revise your definition of Single. "Single" is a real LINQ verb that means "must have exactly one" (btw, DF 2010 supports both Single and SingleOrNullEntity). You defined it as "the first of many ... or the null entity if there are none". These are not the same at all.
So much for my first thoughts. Remember, I'm offering my opinion ... not law. I called out a few absolute "Do Nots" ... but only a few and even here all I'm saying is that you should prepare very good arguments for rejecting my advice. For the most part, just having a repository is more important then doing it one particular way.
Thanks again, Chuck, for pioneering. Pioneers take arrows ... hope you don't mind mine :-)
Ward