Should I use DDD aggregate root repositories with EF 4.1 + LINQ?
Asked Answered
T

1

5

I've read DDD Evans, and I' experimenting with an aggregate root repository design using C# and Entity Framework 4.1 + LINQ.

However, I'm concerned about the actual queries that are being sent to the DB. I'm using SQL 2008 R2, and running SQL Profiler to examine what the DB is doing in response to the LINQ code.

Consider a simple 2 entity design with Person and EmailAddress. One Person can have zero to many EmailAddresses, and an EmailAddress must have exactly one Person. Person is the aggregate root, so there should not be a repository for email addresses. Email addresses should be selected out of the Person repository (according to DDD Evans).

For comparison, I do have a temporary repository set up for email addresses. The following line of code:

var emailString = "[email protected]";
var emailEntity = _tempEmailRepository.All.SingleOrDefault(e => 
    e.Value.Equals(emailString, StringComparison.OrdinalIgnoreCase));

... executes a nice clean SQL query according to the profiler:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Value] AS [Value], 
[Extent1].[IsDefault] AS [IsDefault], 
[Extent1].[IsConfirmed] AS [IsConfirmed], 
FROM [dbo].[EmailAddress] AS [Extent1]

I can select the email out of the person repository, with the following code:

var emailEntity = _personRepository.All.SelectMany(p => p.Emails)
    .SingleOrDefault(e => e.Value.Equals(emailString, 
        StringComparison.OrdinalIgnoreCase))

This gets me the same entity at runtime, but with different commands showing up in the SQL Profiler:

SELECT 
[Extent1].[Id] AS [Id],  
[Extent1].[FirstName] AS [FirstName],  
[Extent1].[LastName] AS [LastName], 
FROM [dbo].[Person] AS [Extent1]

In addition to the above query that selects from Person, there are a number of "RPC:Completed" events, one for each EmailAddress row in the DB:

exec sp_executesql N'SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Value] AS [Value], 
[Extent1].[IsDefault] AS [IsDefault], 
[Extent1].[IsConfirmed] AS [IsConfirmed], 
FROM [dbo].[EmailAddress] AS [Extent1]
WHERE [Extent1].[PersonId] = 
    @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

exec sp_executesql N'SELECT 
[Extent1].[Id] AS [Id], 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Value] AS [Value], 
[Extent1].[IsDefault] AS [IsDefault], 
[Extent1].[IsConfirmed] AS [IsConfirmed], 
FROM [dbo].[EmailAddress] AS [Extent1]
WHERE [Extent1].[PersonId] = 
    @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=2

My test db has 14 rows in dbo.EmailAddress, and there are 14 different RPC:Completed calls, each with a different @EntityKeyValue1 value.

I'm assuming this is bad for SQL performance, since as the dbo.EmailAddress table gets more rows, more of these RPC's will be invoked on the db. Is there another better approach to using DDD aggregate root repositories with EF 4.1 + LINQ?

Update: Solved

The problem was that the All property was returning an IEnumerable<TEntity>. After this was changed to IQueryable<TEntity>, LINQ kicked in and selected the whole Person + Emails in one shot. However, I had to chain in .Include(p => p.Emails) before returning the IQueryable from All.

Tenner answered 18/5, 2011 at 15:23 Comment(2)
What do you return from All property?Isborne
Good question. When I posted this All was returning an IEnumerable<TEntity>. Changed it to IQueryable and saw the difference. Will post update.Tenner
S
12

Given the level of abstraction modern ORMs already give you, I personally would advice against adding an additional layer of abstraction between you and your database. Besides reinventing the wheel, you will find using the chosen ORM in your service layer directly will give you finer grained control over the queries, fetching and caching strategies.

Ayende's series Wages of Sin is a good resource for various other arguments against using Specifications/Repositories with a modern ORM, especially considering that LINQ effectively already gives you almost everything you are likely to need.

I have gone the route of "DDD" on a past project (in quotes because it's bound to the understanding of DDD I had at the time). In hindsight, I realize that it's a shame that in public debate DDD is often reduced to applying these patterns. I've fallen into that trap, and I hope I can help others avoid it.

Repository and Specification are Infrastructure pattern. Infrastructure is there to serve a purpose, not to be a purpose on it's own. When it comes to Infrastructure, I advocate applying the Reused Abstraction Principle rigorously. To give a quick summary, the RAP says you should introduce an abstraction if, and only if it's going to be consumed by more than 2 consumers and that additional layer of abstraction actually implements some behavior. If you only introduce an abstraction to decouple you from something (such as an ORM) be very careful, it is likely you will end up with a leaky abstraction.

The whole point of DDD is to keep your Domain Model separate from your Infrastructure and make your Domain Model as expressive as possible. There's no evidence this can't be achieved without using Repositories. Repositories are only there to hide the details of data access, something an ORM does already. (On a side note, considering the age of the DDD book I don't think the common use of ORMs was in the picture back then). Now, Repositories may be useful to enforce Aggregate roots etc. However, I think this should be treated by making a clear distinction between "read" operations (Queries) and "write" operations (Commands). It's only for the latter that the domain model should be relevant, queries are often better served by tailored (and more flexible) models (such as DTOs or annonymous objects).

The case for Specifications is similar. The intended purpose of Specifications is similar. Their power lies in building the elements of a domain specific language for querying objects. Much of the "glue" that generalized Specification patterns provided to combine those elements has become obsolete with the advent of LINQ. Hint: Take a look at Predicate Builder (<50 Lines of C#), it is likely all you are going to need to implement specifications.

To summarize this lengthy (and hopefully not too disorganized, I will revisit later on I hope) post:

  1. Don't go mad about infrastructure, build it up as you go.
  2. Use your domain model for domain specific behavior, not for supporting your views.
  3. Focus on the more important part of DDD: Use aggregate roots, build up you Ubiquitous Language, ensure good communication with the business experts.
Sectionalism answered 18/5, 2011 at 20:15 Comment(3)
Ive read the book last week, thanks for the clean words/advices. I think i need to reread the book from another viewTarragon
All excellent points for discussion. In our case I think the repository pattern is still justified: When editing data in some entities, the original data is to be preserved in the DB. So, any particular entity can have multiple revisions, only 1 of which is the current version. It would be a violation of the SRP to burden the service layer with these concerns, so we've applied entity layer supertype + repositories (+ UoW) to encapsulate them. The goal now is to keep the repositories as thin, light, and few as possible. Going to go read those Wages of Sin articles now...Tenner
I would have to disagree. Try modeling a simple case of "This object should never have the 'name' property set to 'John'" in ORM. I have yet to find a plausible solution with EF or NHibernate or anything else.Appall

© 2022 - 2024 — McMap. All rights reserved.