2011-05-18 12 views
5

He leído DDD Evans y estoy experimentando con un diseño agregado de repositorio raíz usando C# y Entity Framework 4.1 + LINQ.¿Debo usar repositorios raíz agregados DDD con EF 4.1 + LINQ?

Sin embargo, me preocupan las consultas reales que se envían al DB. Estoy usando SQL 2008 R2 y ejecutando SQL Profiler para examinar lo que está haciendo el DB en respuesta al código LINQ.

Considere un diseño simple de 2 entidades con Person y EmailAddress. Una Persona puede tener cero a muchos EmailAddresses, y una EmailAddress debe tener exactamente una Persona. Person es la raíz agregada, por lo que no debe haber un repositorio para las direcciones de correo electrónico. Las direcciones de correo electrónico deben seleccionarse del repositorio de personas (según DDD Evans).

Para la comparación, tengo un repositorio temporal configurado para direcciones de correo electrónico. La siguiente línea de código:

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

... ejecuta una consulta SQL agradable limpio de acuerdo con el perfilador:

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] 

puedo seleccionar el correo electrónico fuera del depósito de persona, con el siguiente código:

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

Esto me pone la misma entidad en tiempo de ejecución, pero con diferentes comandos a aparecer en el Analizador de SQL:

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

Además de la consulta anterior que selecciona de una persona, hay una serie de "RPC: completadas" eventos, uno para cada fila EmailAddress en el PP:

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 

Mi db prueba tiene 14 filas en dbo.EmailAddress, y hay 14 RPC diferentes: llamadas completadas, cada una con un valor @ EntityKeyValue1 diferente.

Supongo que esto es malo para el rendimiento de SQL, ya que como la tabla dbo.EmailAddress obtiene más filas, se invocarán más de estas RPC en la base de datos. ¿Hay algún otro enfoque mejor para usar los repositorios raíz agregados DDD con EF 4.1 + LINQ?

Actualización: Resuelto

El problema era que el Toda la propiedad se devuelve un IEnumerable<TEntity>. Después de que esto se cambió a IQueryable<TEntity>, LINQ inició y seleccionó toda la Persona + Correos electrónicos en una sola toma. Sin embargo, tuve que encadenar. Incluir (p => p.Emails) antes de devolver IQueryable de All.

+2

¿Qué devuelves de la propiedad 'All'? –

+0

Buena pregunta. Cuando publiqué todo, todo devolvía un IEnumerable . Lo cambió a IQueryable y notó la diferencia. Publicará la actualización. – danludwig

Respuesta

12

Dado el nivel de abstracción que los ORM modernos ya le ofrecen, personalmente le aconsejaría que no agregue una capa adicional de abstracción entre usted y su base de datos. Además de reinventar la rueda, encontrará que usar el ORM elegido en su capa de servicio directamente le dará un control más detallado sobre las estrategias de búsqueda, búsqueda y almacenamiento en caché.

La serie de Ayende Wages of Sin es un buen recurso para varios otros argumentos contra el uso de Especificaciones/Repositorios con un ORM moderno, especialmente considerando que LINQ efectivamente ya le proporciona casi todo lo que probablemente necesite.

He seguido la ruta de "DDD" en un proyecto anterior (entre comillas porque está ligado a la comprensión de DDD que tenía en ese momento).En retrospectiva, me doy cuenta de que es una pena que en el debate público DDD a menudo se reduce a la aplicación de estos patrones. He caído en esa trampa, y espero poder ayudar a otros a evitarla.

Repositorio y especificación son Infraestructura patrón. La infraestructura está ahí para servir a un propósito, no para ser un propósito en sí mismo. Cuando se trata de Infraestructura, recomiendo aplicar el Principio Reused Abstraction rigurosamente. Para dar un resumen rápido, el RAP dice que debe introducir una abstracción si, y solo si va a ser consumida por más de 2 consumidores y esa capa adicional de abstracción realmente implementa algún comportamiento. Si solo introduce una abstracción para desacoplarlo de algo (como un ORM) tenga mucho cuidado, es probable que termine con una abstracción con goteras.

El objetivo de DDD es mantener su Modelo de Dominio separado de su Infraestructura y hacer que su Modelo de Dominio sea lo más expresivo posible. No hay evidencia de que esto no se pueda lograr sin usar repositorios. Los repositorios solo están ahí para ocultar los detalles del acceso a los datos, algo que ya hace ORM. (En una nota al margen, considerando la edad del libro de DDD, no creo que el uso común de los ORM estuviera en la imagen en aquel entonces). Ahora, los repositorios pueden ser útiles para aplicar raíces de Agregado, etc. Sin embargo, creo que esto debe tratarse haciendo una distinción clara entre operaciones de "lectura" (Consultas) y operaciones de "escritura" (Comandos). Es solo para este último que el modelo de dominio debe ser relevante, las consultas a menudo son mejor atendidas por modelos adaptados (y más flexibles) (como DTO u objetos anónimos).

El estuche para las especificaciones es similar. El propósito previsto de las Especificaciones es similar. Su poder radica en construir los elementos de un lenguaje específico de dominio para consultar objetos. Gran parte del "pegamento" que los patrones de Especificación generalizados proporcionaron para combinar esos elementos se ha vuelto obsoleto con la llegada de LINQ. Sugerencia: Eche un vistazo a Predicate Builder (< 50 líneas de C#), es probable que todo lo que necesite para implementar especificaciones.

Para resumir este largo (y espero que no sea demasiado desorganizados, me gustaría volver más adelante espero) mensaje:

  1. No vaya loco por la infraestructura, construirlo a medida que avanza.
  2. Utilice su modelo de dominio para el comportamiento específico del dominio, no para respaldar sus vistas.
  3. Enfóquese en la parte más importante de DDD: utilice raíces agregadas, desarrolle su lenguaje ubicuo, garantice una buena comunicación con los expertos en negocios.
+0

He leído el libro la semana pasada, gracias por las palabras/consejos limpios. Creo que necesito volver a leer el libro desde otra vista – Khh

+0

Todos los puntos excelentes para la discusión. En nuestro caso, creo que el patrón del repositorio aún está justificado: cuando se editan datos en algunas entidades, los datos originales se conservarán en la base de datos. Por lo tanto, cualquier entidad en particular puede tener múltiples revisiones, de las cuales solo 1 es la versión actual. Sería una violación del SRP cargar la capa de servicio con estas preocupaciones, por lo que hemos aplicado supertipo de capa de entidad + repositorios (+ UoW) para encapsularlos. El objetivo ahora es mantener los repositorios lo más delgados, livianos y pocos posibles. Ir a leer esos artículos de Wages of Sin ahora ... – danludwig

+0

Tendría que estar en desacuerdo. Intente modelar un caso simple de "Este objeto nunca debe tener la propiedad 'nombre' establecida en 'Juan'" en ORM. Todavía tengo que encontrar una solución plausible con EF o NHibernate o cualquier otra cosa. – drozzy