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.
All
property? – Isborne