LINQ to entities - best practices for passing models
As we all know most apps have a data access layer, often using repository classes. Typically we want the repository to operate with strongly typed objects, e.g.
interface IUserRespository
{
User GetUserByID(int id);
void AddUser(User u);
... and so on
}
However, sometimes we want to make a more complex query on a DB, that involves grouping and aggregation. For instance we want fetch a total value of all orders of all users (the result set will have only two columns: UserName and TotalAmount). Now my question: what would the repository method look like? There are dozens of examples on the internet of LINQ to entities how to make a query with sum and group by, but all these examples return anonymous types. So, how can our repository return the result of such query? The other common answer is: wrap it in a class. So the method could look like this:
interface IUserRepository
{
UserOrderTotal[] GetOrderTotalsForAllUsers();
}
UserOrderTotal would have to be a small class with the two properties returned by the query: UserName and TotalAmount. This class would be defined somewhere in one of our .cs files. Is this really the only solution? It looks very bad because it introduces a new 'temporary' model somewhere outside the main entities model (edmx). Ideally I'd like to enrich my User model with a new field, called TotalAmount, that would only be populated in this query, so the interface could look like this:
interface IUserRepository
{
User[] GetOrderTotalsForAllUsers();
}
And it would return User entities with all fields set to default, except the Name and TotalAmount. The problem that I have is that I cannot compile a model with field that is not mapped to a database column. I'm getting error Error 3004: Problem in mapping fragments starting at line 2226:No mapping specified for properties User.TotalAmount in Set User.An Entity with Key (PK开发者_如何学运维) will not round-trip when: Entity is type [FPSoMeterModel.User]
Am I doing it wrong? Is this problem trivial, or maybe I'm asking wrong questions? Making a wrapper class for each query (!) that involves aggregation seems a bit ridiculous. It seems that the whole LINQ stuff encourage people NOT to use a multi-tier architecture, but to build queries in the same layer where the data is rendered... How do you guys deal with this, do you use "repository" classes with LINQ, if yes - how do you return complex query results?
I wouldn't consider defining a new UserOrderTotal
class bad practice, on the contrary. There are two ways of looking at such an object.
(1) You can see this UserOrderTotal
class as an Data Transfer Object (DTO). In this way it is not part of your domain. DTOs are normally used for transferring entities over the wire or to supply domain objects in a flattened form to the presentation layer. I use them all the time. In this situation the object is used as a projection on the data: as a view. The MVVM pattern uses this same principle where the ViewModel part of MVVM is a user interface specific DTO.
(2) You can see this UserOrderTotal
class as part of your domain. From a domain driven design perspective the object could be part of the users language. In that situation, it would be a logical consequence to define it as domain object. Difference however is that it will not be defined in de Entity Framework’s (EF) edmx file. I think it is possible in EF to define entities that don’t have a mapping to a database table, but I don’t know if that is making things easier. I personally don't see a problem defining these domain objects outside the edmx file in the same project as where the edmx is located.
About returning an array of User
objects: This seems like a bad idea. There are several problems with that approach. First of all your code isn’t communicating its intent very well. You use a User
object, but leave all properties empty. This practice is very hard to follow for anyone trying to use this code. You are actually abusing the User
object for something it is not: namely an aggregate.
Besides this you noticed that EF can't handle this extra property you added to the model. While in general it is possible to add properties to EF model (by using partial classes) you should be very conservative about this. While things will compile things will fail at runtime when you use these properties in LINQ queries.
Making a wrapper class for each query (!) that involves aggregation seems a bit ridiculous.
I don’t think so. IMO it is the right thing to do. It is the consequence of using EF in a multi-layer architecture. I must admit that having EF in a single layer is much easier, but in the long turn it gets really messy. The bigger the project and the longer you need it to be maintained, the more adding layers of abstractions is justified.
How do you guys deal with this, do you use "repository" classes with LINQ
The application I’ve designed, often have a service / business layer that separates the reads from the mutations. For read operations / queries I use static classes with methods that return a collection of domain objects or DTOs. Often such a 'service method' is specific to a particular part of the user interface. The mutations are wrapped within 'service commands'. The user interface creates a service command and fires it. A command expresses a single use case or user story and is an atomic piece of logic. The service layer will ensure the creation of a database transaction (when needed) when the command is executed. For the user interface, using a command usually looks like this:
void UpdateButton_Click(object sender, EventArgs e)
{
if (!this.Page.IsValid)
{
return;
}
var command = new UpdateUserSettingsCommand();
command.UserName = this.UserName.Text;
command.MailAddress = this.MailAddress.Text;
command.Execute();
}
I hope this all makes sense. If you want to read more about how I use DTOs, read this SO answer of mine.
I hope this helps.
精彩评论