Soy pro-repositorio, aunque me he alejado de los patrones genéricos de repositorio. En cambio, alineo mis repositorios con la función comercial a la que sirven. Los repositorios no están destinados a abstraer el ORM, ya que esto no es algo que espere cambiar, y al mismo tiempo evito hacer un repositorio demasiado granular. (Es decir, CRUDO) En cambio, mis repositorios tienen dos o tres propósitos clave:
- Recuperación de datos
- Creación de datos
- Borrado duro
Para la recuperación de datos, el repositorio siempre regresa IQueryable<TEntity>
. Para la creación de datos, devuelve TEntity. El repositorio maneja mi filtrado de nivel base, como el estado "activo" de autorización para sistemas que usan patrones de borrado suave y el estado "actual" para sistemas que usan datos históricos. La creación de datos es responsable de garantizar que las referencias requeridas se resuelvan y asocien y que la entidad esté configurada y lista para funcionar.
La actualización de datos es responsabilidad de la lógica de negocios que trabaja con las entidades en cuestión. Esto puede incluir cosas como reglas de validación. No intento encapsular eso en un método de repositorio.
La eliminación en la mayoría de mis sistemas es una eliminación suave, por lo que se incluiría en la actualización de datos. (IsActive = false) En el caso de borrados duros, esto sería una línea en el Repositorio.
¿Por qué repositorios? Prueba de habilidad. Claro, DbContext se puede burlar, pero es más simple burlarse de una clase que devuelveIQueryable<TEntity>
. También juegan bien con el patrón UoW, personalmente uso el patrón DbContextScope de Mehdime para determinar la unidad de trabajo en el nivel que quiero (Controladores Ie en MVC) y dejar que mis controladores y clases de servicio auxiliar utilicen los repositorios sin necesidad de pasar referencias a el UoW / dbContext alrededor. El uso de IQueryable significa que no necesita muchos métodos de envoltura en el repositorio, y su código puede optimizar cómo se van a consumir los datos. Por ejemplo, el repositorio no necesita exponer métodos como "Existe" o "Contar", o tratar de envolver entidades con otras POCO en los casos en que desee subconjuntos de datos. Ni siquiera necesitan manejar opciones de carga ansiosa para datos relacionados que puede o no necesitar. Al pasar IQueryable, el código de llamada puede:
.Any()
.Count()
.Include() // Generally avoided, instead I use .Select()
.Where()
.Select(x => new ViewModel or Anon. Type)
.Skip().Take()
.FirstOrDefault() / .SingleOrDefault() / .ToList()
Muy flexible, y desde un punto de vista de prueba, mi repositorio simulado simplemente necesita devolver Listas de objetos de entidad poblados.
En cuanto a los repositorios genéricos, me he alejado de estos en su mayor parte porque cuando terminas con un repositorio por tabla, tus controladores / servicios terminan con referencias a varios repositorios para hacer una acción comercial. En la mayoría de los casos, solo uno o dos de estos repositorios realizan operaciones de escritura (siempre que esté utilizando las propiedades de navegación correctamente), mientras que el resto admite aquellos con Lecturas. Prefiero tener algo así como un repositorio de pedidos que sea capaz de leer y crear pedidos, y leer cualquier búsqueda relevante, etc. (objetos de clientes livianos, productos, etc.) como referencia al crear un pedido, que alcanzar 5 o 6 repositorios diferentes. Puede violar los puristas de DNRY, pero mi argumento es que el propósito del repositorio es servir a la creación de Órdenes que incluye las referencias relacionadas.Repository<Product>
para obtener productos donde para la base de un pedido solo necesito una entidad con un puñado de campos. Mi OrderRepository puede tener un .GetProducts()
método IQueryable<ProductSummary>
que devuelve, lo que me parece más agradable que uno Repository<Product>
que termina teniendo varios métodos "Get" para probar y servir diferentes áreas de las necesidades de la aplicación, y / o alguna expresión compleja de filtrado de paso.
Opto por un código más simple que sea fácil de seguir, probar y ajustar. Se puede abusar de malas maneras, pero prefiero tener algo donde los abusos sean fáciles de detectar y corregir que tratar de "bloquear" el código de una manera que no se pueda abusar, fallar y luego tener algo que sea una pesadilla para poder hacer lo que el cliente paga para que lo haga al final. :)