linq-to-sql repository domain-driven-design iqueryable

linq to sql - ASP.MVC: Repositorio que refleja IQueryable pero no Linq a SQL, DDD Cómo cuestionar



linq-to-sql repository (2)

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?

Soy muy nuevo en esto, así 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 código de Linq a 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) ¿cuál sería mi tipo de datos modelo y cómo hacer referencia desde la vista?

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

Editar:

Cottsak habilitó el código y lo hizo funcionar, 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 de Modelo fueron accedidos por:

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

Y esto condujo a un

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

Gracias a ambos por las respuestas.


Pruebe 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 lugar 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 de Announcement asociados para cada categoría, pero de forma que se puedan cargar de forma diferida con IQueryable (es decir, cuando finalmente llame a esta propiedad). , no tengo que llamar a toda la colección. Puedo hacer más LINQ sobre esto para filtrar).


Suponiendo que las clases de LINQ a SQL (L2S) se generan automáticamente y reflejan su base de datos subyacente, la respuesta breve es: No exponga IQueryable de ninguna de sus clases L2S, sería una Absolución furtiva.

La respuesta un poco más larga:

El objetivo de los repositorios es ocultar el acceso a los datos detrás de una abstracción para que pueda reemplazar o modificar su código de acceso a 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 deben definirse de una manera neutral en tecnología. Esto se realiza mejor como Objetos simples de C # (POCO).

Además, exponer IQueryable te da 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 una 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 la ID debe definirse. Podríamos escribir una clase de entidad base como esta:

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 aplica muy bien sus invariantes (en este caso, la ID 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 tiene sentido hidratarlo en su totalidad, porque esa es la única forma en que puede satisfacer sus invariantes.

En cualquier caso, si con frecuencia descubre 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 del Entity Framework, debemos obtener el respaldo 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, vea IQueryable es Tight Coupling .