2009-11-09 11 views
11

Quiero crear un repositorio DDD que devuelva entidades IQueryable que coincidan con las clases subyacentes de Linq a SQL, menos cualquier relación. Puedo devolver fácilmente entidades menos las relaciones con una nueva selección de campo, campo, ...} de Linq. ¿Cómo codigo la clase Entidad de repositorio? ¿Cómo puedo devolver un objeto del repositorio que tiene la clase de repositorio, no la clase de Linq a SQL, y aún así llenarlo con múltiples entidades de la selección de Linq? ¿Cómo haría referencia a esta clase devuelta en mi ViewModel?ASP.MVC: Repositorio que refleja IQueryable pero no Linq a SQL, DDD How to question

Soy muy nuevo en esto, por lo tanto, los errores obvios. ¿Me estoy perdiendo el bote y solo debería devolver Entidades completas del depósito, no una proyección? Todavía necesitaría quitar las relaciones de Linq a SQL antes de enviarlas desde el repositorio. ¿Estoy totalmente fuera de la base? Realmente quiero mantener el tipo de datos IQueryable.

Por ejemplo, mi LINQ a código SQL en mi repositorio:

public class MiniProduct 
{ 
    public MiniProduct(string ProductNo, string Name, string Description, double Price) 
    { this.ProductNo = ProductNo; 
     this.Name = Name; 
     this.Description = Description; 
     this.Price = Price; 
    } 
} 

public IQueryable<MiniProduct> GetProductsByCategory(string productCategory) 
{ 
    return (from p in db.Products 
      from c in db.Categories 
      from pc in db.ProductCategories 
      where c.CategoryName == productCategory && 
        c.CatID == pc.CatID && 
        pc.ProductID == p.ProductID 
      select new { p.ProductNo, p.Name, p.Description, p.Price }); 
    // how to return IQueryable<MiniProduct> instead of IQueryable<AnonymousType>??? 
} 

Y en la vista (tratando de escribir fuertemente ViewModel) lo que sería mi modelo de tipo de datos y cómo hacer referencia desde el punto de vista?

<% Page Inherits="System.Web.Mvc.ViewPage<MyStore.Models.MiniProduct>" %> 

Editar:

Cottsak facultado el código y lo hizo trabajar, por lo que gana la casilla de verificación. Sin embargo, Mark Seemann señaló que esta técnica causará efectos secundarios. Tenía razón en que proyectar o sub-establecer su POCO es malo. Después de hacer que el código funcionara, terminé haciendo una tonelada más de objetos de entidad, lo que causa complicaciones innecesarias. Finalmente cambié el código para reflejar las sugerencias de Mark.

Para agregar a las sugerencias de Cottsak: El valor de retorno de mi repositorio era IQueryable. El tipo de referencia del modelo de directiva de página era

Inherits="System.Web.Mvc.ViewPage<IQueryable<MyStore.Models.MiniProduct>>" 

los campos del modelo se accede por:

Model.SingleOrDefault().ProductNo 
Model.SingleOrDefault().Name 
... 

Y esto llevó a una

foreach (MyStore.Models.MiniProduct myproduct in Model) {} 

Gracias a ambos por las respuestas.

+0

Tiene la idea de vista fuertemente tipada correcta. Tu ejemplo es correcto. –

Respuesta

8

intentar algo como esto:

public class AnnouncementCategory : //... 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
} 

.. y en tu repositorio:

public IQueryable<AnnouncementCategory> GetAnnouncementCategories() 
{ 
    return from ac in this._dc.AnnouncementCategories 
      let announcements = this.GetAnnouncementsByCategory(ac.ID) 
      select new AnnouncementCategory 
      { 
       ID = ac.ID, 
       Name = ac.Text, 
       Announcements = new LazyList<Announcement>(announcements) 
      }; 
} 

private IQueryable<Announcement> GetAnnouncementsByCategory(int categoryID) 
{ 
    return GetAnnouncements().Where(a => a.Category.ID == categoryID); 
} 

De esta manera, en vez de proyectar en un tipo anónimo, estoy proyectando en una nueva instancia de mi clase AnnouncementCategory. Puede ignorar la función GetAnnouncementsByCategory si lo desea, esto se utiliza para encadenar la colección de objetos Announcement asociados para cada categoría, pero de forma que se puedan cargar de forma diferida con IQueryable (es decir, cuando yo haga eventualmente llame al esta propiedad, no tengo que llamar a toda la colección. Puedo hacer más LINQ sobre esto para filtrar).

+0

Avíseme si necesita más información sobre el encadenamiento de gráficos de objetos con clases de carga diferida. Es muy divertido –

+0

Wow. Voy a imprimir esto y ponerlo en mi muro del código más genial que haya visto en mi vida. Usando la instrucción let define un conjunto, la carga lenta que se establece más adelante en la misma consulta es realmente notable. ¿Dónde encuentras esos patrones? –

+0

Wow ... no pensé que fuera tan increíble. Pero me llevó bastante investigación encontrar la parte "let" - antes de usar let (y la función separada, que por cierto es absolutamente necesaria para este 2 trabajo) todo funcionaría, excepto la consulta perezosa (diferida ejecución). Sin el uso de let y la función, todos los objetos encadenados en el gráfico realizarían consultas inmediatamente (como una enumeración se desencadena con LinqToSql). Este método está documentado por MS y, por lo que sé, es la única forma de asignar la ejecución diferida a otro patrón de inicio diferido. Las clases perezosas que uso: http://bit.ly/1Aaxx5 –

29

Suponiendo que sus clases de LINQ to SQL (L2S) son generados automáticamente y refleja su base de datos subyacente, la respuesta corta es: No exponga IQueryable de cualquiera de sus clases L2S - que sería una Leaky Abstracción.

La respuesta ligeramente más largo:

El punto de repositorios es ocultar el acceso a datos detrás de una abstracción para que pueda reemplazar o modificar el código de acceso a los datos independientemente de su modelo de dominio. Eso no será posible cuando base la interfaz del Repositorio en tipos definidos dentro de una implementación específica (su Componente de Acceso a Datos (DAC) basado en L2S) - incluso si pudiera proporcionar una nueva implementación de su interfaz de Repositorio, necesitaría referencia su L2S DAC. Eso no funcionaría especialmente bien si de repente decidiera cambiar a LINQ a entidades, o al servicio de almacenamiento de tablas de Azure.

Los objetos de dominio se deben definir de una manera neutral con respecto a la tecnología. Esto se realiza mejor como Objetos simples de C# (POCO).

Además, exponer IQueryable le brinda la oportunidad de realizar proyecciones. Eso puede sonar atractivo, pero en realidad es bastante peligroso en un contexto DDD.

En DDD, debemos diseñar objetos de dominio para que encapsulen la lógica de dominio y aseguren todas las invariantes.

Como ejemplo, considere el concepto de Entidad (no un LINQ para la Entidad de Entidades, sino una Entidad de DDD). Las entidades casi siempre se identifican mediante una ID persistente, por lo que una invariante común es que se debe definir el ID . Podríamos escribir una clase de entidad de base de esta manera:

public abstract class Entity 
{ 
    private readonly int id; 

    protected Entity(int id) 
    { 
     if(id <= 0) 
     { 
      throw new ArgumentOutOfRangeException(); 
     } 
     this.id = id; 
    } 

    public int Id 
    { 
     get { return this.id; } 
    } 
} 

Tal clase hace cumplir bien sus invariantes (en este caso que la identificación debe ser un número positivo). Puede haber muchas otras invariantes más específicas del dominio implementadas en clases particulares, pero es probable que muchas de ellas estén en desacuerdo con el concepto de proyección: es posible que pueda definir una proyección que omita ciertas propiedades (como el ID), pero se bloqueará en tiempo de ejecución porque los valores predeterminados para los tipos (como 0 para int) arrojarán excepciones.

En otras palabras, incluso si solo necesita ciertas propiedades de un objeto de dominio, solo tendrá sentido hidratarlo en su totalidad, porque esa es la única forma en que puede satisfacer sus invariantes.

En cualquier caso, si con frecuencia encuentra que debe seleccionar solo ciertas partes de sus objetos de dominio, puede ser porque violan el principio de responsabilidad única. Podría ser una mejor idea derramar la clase de dominio en dos o más clases.

En conclusión, exponer IQueryable suena como una estrategia muy atractiva, pero con la implementación actual de L2S conducirá a Absorción de fugas y Modelos de dominio anémico.

Con la próxima versión de Entity Framework, debemos obtener soporte de POCO, por lo que puede acercarnos más a ese objetivo. Sin embargo, en lo que a mí respecta, el jurado todavía está fuera de esa cuenta.


Para una discusión más a fondo de un tema muy similar, véase IQueryable is Tight Coupling.

+0

Una gran mejora para mi aprendizaje fue su comentario sobre nunca sub-establecer clases de repositorio junto con la eliminación de Linq a IQueryable de Sql. Sin embargo, ¿la clase de POCO que creamos no actúa como un cortador de galletas que elimina el Linq original a Sql IQueryable y las relaciones, creando un nuevo objeto IQueryable?No creo que esto sea diferente de List . Esto también le permite devolver cosas como LazyList (objeto IQueryable ), o ¿cree que debería ser una capa encima del Repositorio, como AOP o problemas de corte transversal? –

+0

@ Zim: No creo que eso sea lo que dice Mark. No soy tan claro con DDD como Mark, pero creo que los principios básicos de DDD parecen violados en la opinión de Mark: a saber, encapsulando correctamente el 'Dominio' dentro del Modelo de Dominio (datos y comportamiento [Fowler]). Uno de los más fáciles. Puedo ver cómo habilitar la proyección de L2S en objetos POCO (¿Dominio?) Podría dificultar el diseño de Objetos de Dominio en una forma DDD, sin embargo, estaría feliz de intentar justificar mi implementación de un Repo "IQueryable" devolviendo restricciones DDD con fines prácticos ... –

+0

... Añado la advertencia de 'propósitos prácticos' porque sé que los puristas de DDD podrían no estar de acuerdo con ciertos aspectos de mi diseño, pero creo que abarca -suficiente- DDD para beneficiarse de DDD y ser práctico con el tecnologías disponibles. Con referencia específica a LinqToSql, confesaría que 'IQueryable' corre el riesgo de acoplar el modelo POCO estrechamente a las diferencias LinqToSql con LINQ puro, pero también estoy seguro de que mis clases 'Lazy *' eliminan apropiadamente este acoplamiento de modo que mi modelo vuela bajo su propio vapor. –