He seguido muchos caminos y he creado muchas implementaciones de repositorios en diferentes proyectos y ... he tirado la toalla y me he rendido, he aquí por qué.
Codificación de la excepción
¿Codifica para el 1% de probabilidad de que su base de datos cambie de una tecnología a otra? Si está pensando en el estado futuro de su empresa y dice que sí, que es una posibilidad, entonces a) deben tener mucho dinero para pagar la migración a otra tecnología DB ob) está eligiendo una tecnología DB por diversión oc ) algo salió terriblemente mal con la primera tecnología que decidió utilizar.
¿Por qué desechar la rica sintaxis de LINQ?
LINQ y EF se desarrollaron para que pudiera hacer cosas interesantes con él para leer y recorrer gráficos de objetos. Crear y mantener un repositorio que pueda brindarle la misma flexibilidad para hacerlo es una tarea monstruosa. En mi experiencia, cada vez que he creado un repositorio, SIEMPRE he tenido una filtración de lógica empresarial en la capa del repositorio para hacer que las consultas sean más eficaces y / o reducir el número de accesos a la base de datos.
No quiero crear un método para cada permutación de una consulta que tengo que escribir. También podría escribir procedimientos almacenados. No quiero GetOrder
, GetOrderWithOrderItem
, GetOrderWithOrderItemWithOrderActivity
, GetOrderByUserId
, y así sucesivamente ... Yo sólo quiero llegar a la entidad principal y transversal e incluir el gráfico de objetos como yo así que por favor.
La mayoría de los ejemplos de repositorios son una mierda
A menos que esté desarrollando algo REALMENTE básico como un blog o algo, sus consultas nunca serán tan simples como el 90% de los ejemplos que encuentre en Internet en torno al patrón de repositorio. ¡No puedo enfatizar esto lo suficiente! Esto es algo que uno tiene que arrastrarse por el barro para darse cuenta. Siempre habrá una consulta que rompa su repositorio / solución perfectamente pensado que ha creado, y no es hasta ese punto en el que se cuestiona y comienza la deuda / erosión técnica.
No me pruebes unitario hermano
Pero, ¿qué pasa con las pruebas unitarias si no tengo un repositorio? ¿Cómo me burlaré? Simple, no es así. Veámoslo desde ambos ángulos:
Sin repositorio: puede simular el DbContext
uso de uno IDbContext
u otros trucos, pero luego está realmente probando LINQ to Objects y no LINQ to Entities porque la consulta se determina en tiempo de ejecución ... OK, ¡eso no es bueno! Así que ahora depende de la prueba de integración cubrir esto.
Con repositorio: ahora puede simular sus repositorios y realizar pruebas unitarias en las capas intermedias. Genial, ¿verdad? Bueno, no realmente ... En los casos anteriores en los que tiene que filtrar la lógica en la capa del repositorio para hacer que las consultas sean más eficaces y / o menos aciertos en la base de datos, ¿cómo pueden cubrir eso sus pruebas unitarias? Ahora está en la capa de repositorio y no quiere probarIQueryable<T>
¿verdad? También seamos honestos, sus pruebas unitarias no cubrirán las consultas que tienen una .Where()
cláusula de 20 líneas y.Include()
es un montón de relaciones y vuelve a la base de datos para hacer todas estas otras cosas, bla, bla, bla de todos modos porque la consulta se genera en tiempo de ejecución. Además, dado que creó un repositorio para mantener ignorante la persistencia de las capas superiores, si ahora desea cambiar la tecnología de su base de datos, lo siento, sus pruebas unitarias definitivamente no garantizarán los mismos resultados en tiempo de ejecución, volviendo a las pruebas de integración. Así que todo el tema del repositorio parece extraño ...
2 centavos
Ya perdemos mucha funcionalidad y sintaxis cuando usamos EF sobre procedimientos almacenados simples (inserciones masivas, eliminaciones masivas, CTE, etc.) pero también codifico en C # para no tener que escribir binario. Usamos EF para que podamos tener la posibilidad de usar diferentes proveedores y trabajar con gráficos de objetos de una manera agradable entre muchas cosas. Algunas abstracciones son útiles y otras no.
Coding for the exception
: Usar repositorios no significa poder cambiar de motor de base de datos. Se trata de separar los negocios de la perseverancia.DbSet
es el repositorio yDbContext
es la Unidad de Trabajo . ¿Por qué implementar el patrón de repositorio cuando ORM ya lo hace por nosotros? Para la prueba, simplemente cambie el proveedor aInMemory
. ¡Y haz tus pruebas! Está bien documentado en MSDN.El patrón de repositorio es una abstracción . Su propósito es reducir la complejidad y hacer que el resto del código permanezca ignorante. Como beneficio adicional, le permite escribir pruebas unitarias en lugar de pruebas de integración .
El problema es que muchos desarrolladores no comprenden el propósito de los patrones y crean repositorios que filtran información específica de persistencia hasta la persona que llama (generalmente exponiendo
IQueryable<T>
). Al hacerlo, no obtienen ningún beneficio sobre el uso directo del OR / M.Actualizar para abordar otra respuesta
Codificación de la excepción
El uso de repositorios no se trata de poder cambiar la tecnología de persistencia (es decir, cambiar la base de datos o usar un servicio web, etc.). Se trata de separar la lógica empresarial de la persistencia para reducir la complejidad y el acoplamiento.
Pruebas unitarias vs pruebas de integración
No escribe pruebas unitarias para repositorios. período.
Pero al introducir repositorios (o cualquier otra capa de abstracción entre la persistencia y el negocio) puede escribir pruebas unitarias para la lógica empresarial. es decir, no tiene que preocuparse de que sus pruebas fallen debido a una base de datos configurada incorrectamente.
En cuanto a las consultas. Si usa LINQ, también debe asegurarse de que sus consultas funcionen, al igual que tiene que hacer con los repositorios. y eso se hace mediante pruebas de integración.
La diferencia es que si no ha mezclado su negocio con declaraciones LINQ, puede estar 100% seguro de que es su código de persistencia el que está fallando y no otra cosa.
Si analiza sus pruebas, también verá que son mucho más limpias si no tiene preocupaciones mixtas (es decir, LINQ + lógica empresarial)
Ejemplos de repositorio
La mayoría de los ejemplos son una mierda. eso es muy cierto. Sin embargo, si busca en Google cualquier patrón de diseño, encontrará muchos ejemplos horribles. Esa no es razón para evitar el uso de un patrón.
Construir una implementación de repositorio correcta es muy fácil. De hecho, solo tienes que seguir una única regla:
No agregue nada a la clase de repositorio hasta el momento en que lo necesite
Muchos programadores son vagos e intentan crear un repositorio genérico y usar una clase base con muchos métodos que podrían necesitar. YAGNI. Escribe la clase de repositorio una vez y la conserva mientras la aplicación esté activa (pueden ser años). ¿Por qué joderlo siendo vago? Manténgalo limpio sin ninguna herencia de clase base. Hará que sea mucho más fácil de leer y mantener.
(La declaración anterior es una pauta y no una ley. Una clase base puede estar muy motivada. Piense antes de agregarla, para que la agregue por las razones correctas)
Cosas viejas
Conclusión:
Si no le importa tener declaraciones LINQ en su código comercial ni le preocupan las pruebas unitarias, no veo ninguna razón para no usar Entity Framework directamente.
Actualizar
He escrito en un blog sobre el patrón de repositorio y lo que realmente significa "abstracción": http://blog.gauffin.org/2013/01/repository-pattern-done-right/
Actualización 2
Nuevamente: una entidad con más de 20 campos está modelada incorrectamente. Es una entidad de DIOS. Descomponerlo.
No estoy argumentando que
IQueryable
no fue hecho para hacer consultas. Estoy diciendo que no es adecuado para una capa de abstracción como el patrón Repository, ya que tiene fugas. No hay un proveedor LINQ To Sql 100% completo (como EF).Todos tienen aspectos específicos de implementación, como cómo usar la carga ansiosa / diferida o cómo hacer declaraciones SQL "IN". Exponer
IQueryable
en el repositorio obliga al usuario a conocer todas esas cosas. Por tanto, todo el intento de abstraer la fuente de datos es un completo fracaso. Simplemente agrega complejidad sin obtener ningún beneficio sobre el uso directo del OR / M.Implemente el patrón de repositorio correctamente o simplemente no lo use en absoluto.
(Si realmente desea manejar entidades grandes, puede combinar el patrón Repository con el patrón Specification . Eso le brinda una abstracción completa que también se puede probar).
fuente
En mi opinión, tanto la
Repository
abstracción como laUnitOfWork
abstracción tienen un lugar muy valioso en cualquier desarrollo significativo. La gente discutirá sobre los detalles de implementación, pero así como hay muchas formas de desollar un gato, hay muchas formas de implementar una abstracción.Su pregunta es específicamente para usar o no usar y por qué.
Como sin duda se habrá dado cuenta de que ya tiene ambos patrones integrados en Entity Framework,
DbContext
es elUnitOfWork
yDbSet
es elRepository
. Por lo general no necesita prueba de la unidad delUnitOfWork
o deRepository
ellos mismos, ya que son simplemente facilitando entre las clases y las implementaciones de acceso a datos subyacentes. Lo que tendrá que hacer una y otra vez es burlarse de estas dos abstracciones cuando pruebe unitariamente la lógica de sus servicios.Puede simular, falsificar o lo que sea con bibliotecas externas que agregan capas de dependencias de código (que no controla) entre la lógica que realiza la prueba y la lógica que se prueba.
Entonces, un punto menor es que tener su propia abstracción para
UnitOfWork
yRepository
le brinda el máximo control y flexibilidad al burlarse de sus pruebas unitarias.Todo muy bien, pero para mí, el poder real de estas abstracciones es que proporcionan una forma sencilla de aplicar técnicas de Programación Orientada a Aspectos y adherirse a los principios SOLID .
Entonces tienes tu
IRepository
:Y su implementación:
Nada fuera de lo común hasta ahora, pero ahora queremos agregar algunos registros, fácil con un Decorador de registros .
Todo hecho y sin cambios en nuestro código existente . Hay muchas otras preocupaciones transversales que podemos agregar, como el manejo de excepciones, el almacenamiento en caché de datos, la validación de datos o lo que sea, y a lo largo de nuestro proceso de diseño y construcción, lo más valioso que tenemos nos permite agregar características simples sin cambiar ninguno de nuestro código existente. es nuestra
IRepository
abstracción .Ahora, muchas veces he visto esta pregunta en StackOverflow: "¿cómo se hace que Entity Framework funcione en un entorno de múltiples inquilinos?".
https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant
Si tiene una
Repository
abstracción, la respuesta es "es fácil agregar un decorador"En mi opinión, siempre debe colocar una simple abstracción sobre cualquier componente de terceros al que se hará referencia en más de un puñado de lugares. Desde esta perspectiva, un ORM es el candidato perfecto, ya que se hace referencia en gran parte de nuestro código.
La respuesta que normalmente me viene a la mente cuando alguien dice "¿por qué debería tener una abstracción (por ejemplo
Repository
) sobre esta o aquella biblioteca de terceros" es "por qué no lo harías tú?"Los decoradores PS son extremadamente sencillos de aplicar utilizando un contenedor IoC, como SimpleInjector .
fuente
En primer lugar, como sugiere alguna respuesta, EF en sí mismo es un patrón de repositorio, no es necesario crear más abstracción solo para nombrarlo como repositorio.
Repositorio simulado para pruebas unitarias, ¿realmente lo necesitamos?
Dejamos que EF se comunique para probar la base de datos en pruebas unitarias para probar nuestra lógica empresarial directamente contra la base de datos de prueba SQL. No veo ningún beneficio de tener una simulación de ningún patrón de repositorio en absoluto. ¿Qué hay de malo en realizar pruebas unitarias en una base de datos de prueba? Como es, las operaciones masivas no son posibles y terminamos escribiendo SQL sin formato. SQLite en memoria es el candidato perfecto para realizar pruebas unitarias contra una base de datos real.
Abstracción innecesaria
¿Desea crear un repositorio solo para que en el futuro pueda reemplazar fácilmente EF con NHbibernate, etc. o cualquier otra cosa? Suena genial, pero ¿es realmente rentable?
¿Linq mata las pruebas unitarias?
Me gustaría ver algunos ejemplos sobre cómo puede matar.
Inyección de dependencia, IoC
Vaya, estas son palabras geniales, seguro que se ven geniales en teoría, pero a veces tienes que elegir entre un diseño excelente y una solución excelente. Usamos todo eso, y terminamos tirando todo a la basura y eligiendo un enfoque diferente. El tamaño frente a la velocidad (tamaño del código y velocidad de desarrollo) es muy importante en la vida real. Los usuarios necesitan flexibilidad, no les importa si su código tiene un diseño excelente en términos de DI o IoC.
A menos que esté compilando Visual Studio
Todos estos grandes diseños son necesarios si está creando un programa complejo como Visual Studio o Eclipse que será desarrollado por muchas personas y debe ser altamente personalizable. Todos los grandes patrones de desarrollo se hicieron realidad después de años de desarrollo por los que han pasado estos IDE, y han evolucionado en un lugar donde todos estos excelentes patrones de diseño son tan importantes. Pero si está haciendo una nómina simple basada en la web o una aplicación comercial simple, es mejor que evolucione en su desarrollo con el tiempo, en lugar de dedicar tiempo a construirlo para millones de usuarios donde solo se implementará para cientos de usuarios.
Repositorio como vista filtrada - ISecureRepository
Por otro lado, el repositorio debe ser una vista filtrada de EF que protege el acceso a los datos aplicando el relleno necesario según el usuario / rol actual.
Pero hacerlo complica aún más el repositorio, ya que termina en una enorme base de código que mantener. Las personas terminan creando diferentes repositorios para diferentes tipos de usuarios o combinaciones de tipos de entidades. No solo esto, también terminamos con muchos DTO.
La siguiente respuesta es una implementación de ejemplo de Repositorio filtrado sin crear un conjunto completo de clases y métodos. Puede que no responda a la pregunta directamente, pero puede ser útil para derivar una.
Descargo de responsabilidad: soy autor de Entity REST SDK.
http://entityrestsdk.codeplex.com
Teniendo en cuenta lo anterior, desarrollamos un SDK que crea un repositorio de vista filtrada basado en SecurityContext que contiene filtros para operaciones CRUD. Y solo dos tipos de reglas simplifican cualquier operación compleja. Primero es el acceso a la entidad y otro es la regla de lectura / escritura para la propiedad.
La ventaja es que no reescribe la lógica empresarial o los repositorios para diferentes tipos de usuarios, simplemente los bloquea o les concede el acceso.
Estas reglas LINQ se evalúan contra la base de datos en el método SaveChanges para cada operación, y estas reglas actúan como cortafuegos frente a la base de datos.
fuente
Hay mucho debate sobre qué método es el correcto, así que lo veo como ambos son aceptables, así que uso el que más me gusta (que no es un repositorio, UoW).
En EF UoW se implementa a través de DbContext y los DbSets son repositorios.
En cuanto a cómo trabajar con la capa de datos, simplemente trabajo directamente en el objeto DbContext, para consultas complejas crearé métodos de extensión para la consulta que se pueden reutilizar.
Creo que Ayende también tiene algunas publicaciones sobre lo malo que es abstraer las operaciones CUD.
Siempre hago una interfaz y tengo mi contexto heredado de ella para poder usar un contenedor IoC para DI.
fuente
Lo que más se aplica a EF no es un patrón de repositorio. Es un patrón de fachada (abstrae las llamadas a los métodos EF en versiones más simples y fáciles de usar).
EF es el que aplica el patrón de repositorio (y también el patrón de unidad de trabajo). Es decir, EF es el que abstrae la capa de acceso a datos para que el usuario no tenga idea de que está tratando con SQLServer.
Y además, la mayoría de los "repositorios" sobre EF ni siquiera son buenas fachadas, ya que simplemente se asignan, de manera bastante directa, a métodos únicos en EF, incluso hasta el punto de tener las mismas firmas.
Las dos razones, entonces, para aplicar este patrón llamado "Repositorio" sobre EF es permitir una prueba más fácil y establecer un subconjunto de llamadas "enlatadas". No está mal en sí mismo, pero claramente no es un repositorio.
fuente
Linq es un 'Repositorio' hoy en día.
ISession + Linq ya es el repositorio y no necesita
GetXByY
métodos niQueryData(Query q)
generalizaciones. Siendo un poco paranoico con el uso de DAL, sigo prefiriendo la interfaz de repositorio. (Desde el punto de vista de la capacidad de mantenimiento, también debemos tener cierta fachada sobre interfaces de acceso a datos específicas).Aquí está el repositorio que usamos: nos desvincula del uso directo de nhibernate, pero proporciona una interfaz linq (como acceso a ISession en casos excepcionales, que eventualmente están sujetos a refactorización).
fuente
El Repositorio (o como se elija llamarlo) en este momento para mí se trata principalmente de abstraer la capa de persistencia.
Lo uso junto con objetos de consulta, por lo que no tengo un acoplamiento con ninguna tecnología en particular en mis aplicaciones. Y también facilita mucho las pruebas.
Entonces, tiendo a tener
Posiblemente agregue métodos asincrónicos con devoluciones de llamada como delegados. El repositorio es fácil de implementar de forma genérica , por lo que no puedo tocar una línea de la implementación de una aplicación a otra. Bueno, esto es cierto al menos cuando usé NH, también lo hice con EF, pero me hizo odiar EF. 4. La conversación es el comienzo de una transacción. Muy bueno si algunas clases comparten la instancia del repositorio. Además, para NH, un repositorio en mi implementación equivale a una sesión que se abre en la primera solicitud.
Luego, los objetos de consulta
Para la configuración utilizo en NH solo para pasar en la ISession. En EF no tiene más ni menos sentido.
Un ejemplo de consulta sería .. (NH)
Para hacer una consulta EF, tendría que tener el contexto en la base abstracta, no la sesión. Pero, por supuesto, el ifc sería el mismo.
De esta manera, las consultas se encapsulan en sí mismas y se pueden probar fácilmente. Lo mejor de todo es que mi código se basa solo en interfaces. Todo esta muy limpio. Los objetos de dominio (negocios) son solo eso, por ejemplo, no hay una mezcla de responsabilidades como cuando se usa el patrón de registro activo que es difícilmente comprobable y mezcla el código de acceso a datos (consulta) en el objeto de dominio y, al hacerlo, se mezclan preocupaciones (objeto que recupera ¿¿sí mismo??). Todo el mundo sigue siendo libre de crear POCO para la transferencia de datos.
Con todo, se proporciona mucha reutilización y simplicidad de código con este enfoque sin perder nada que pueda imaginar. ¿Algunas ideas?
Y muchas gracias a Ayende por sus excelentes publicaciones y continua dedicación. Son sus ideas aquí (objeto de consulta), no las mías.
fuente
Para mí, es una decisión simple, con relativamente pocos factores. Los factores son:
Entonces, si mi aplicación no puede justificar el n. ° 2, los modelos de datos y de dominio separados, por lo general no me molestaré con el n. ° 5.
fuente