¿Por qué no debería usar el patrón de repositorio con Entity Framework?

203

Durante una entrevista de trabajo, me pidieron que explicara por qué el patrón de repositorio no es un buen patrón para trabajar con ORM como Entity Framework. ¿Por qué es este el caso?

StringBuilder
fuente
6060
fue una pregunta
capciosa
2
Probablemente le habría respondido al entrevistador que Microsoft usa el patrón de repositorio muy a menudo mientras demuestra el marco de la entidad: | .
Laurent Bourgault-Roy
1
Entonces, ¿cuál fue la razón del entrevistador para que no sea una buena idea?
Bob Horn
3
El hecho curioso es que la búsqueda de "patrón de repositorio" en Google da los resultados que están relacionados principalmente con Entity Framework y cómo usar el patrón con EF.
Arseni Mourzenko
2
consulte el blog de ayende ayende.com/blog . Basándome en lo que sé, solía usar el Patrón de repositorio, pero finalmente lo abandonó a favor del Patrón de objetos de consulta
Jaime Sangcap,

Respuestas:

99

No veo ninguna razón para que el patrón Repository no funcione con Entity Framework. El patrón de repositorio es una capa de abstracción que coloca en su capa de acceso a datos. Su capa de acceso a datos puede ser cualquier cosa, desde procedimientos almacenados ADO.NET puros hasta Entity Framework o un archivo XML.

En sistemas grandes, donde tiene datos provenientes de diferentes fuentes (base de datos / XML / servicio web), es bueno tener una capa de abstracción. El patrón de repositorio funciona bien en este escenario. No creo que Entity Framework sea suficiente abstracción para ocultar lo que sucede detrás de escena.

He utilizado el patrón de repositorio con Entity Framework como mi método de capa de acceso a datos y todavía tengo que enfrentar un problema.

Otra ventaja de abstraerlo DbContextcon un repositorio es la capacidad de prueba unitaria . Puede tener su IRepositoryinterfaz con 2 implementaciones, una (el Repositorio real) que utiliza DbContextpara comunicarse con la base de datos y la segunda, FakeRepositoryque puede devolver objetos en memoria / datos simulados. Esto hace que su IRepositoryunidad sea comprobable, por lo tanto, otras partes del código que utiliza IRepository.

public interface IRepository
{
  IEnumerable<CustomerDto> GetCustomers();
}
public EFRepository : IRepository
{
  private YourDbContext db;
  private EFRepository()
  {
    db = new YourDbContext();
  }
  public IEnumerable<CustomerDto> GetCustomers()
  {
    return db.Customers.Select(f=>new CustomerDto { Id=f.Id, Name =f.Name}).ToList();
  }
}
public MockRepository : IRepository
{
  public IEnumerable<CustomerDto> GetCustomers()
  {
    // to do : return a mock list of Customers
    // Or you may even use a mocking framework like Moq
  }
}

Ahora usando DI, obtienes la implementación

public class SomeService
{
  IRepository repo;
  public SomeService(IRepository repo)
  {
     this.repo = repo;
  }  
  public void SomeMethod()
  {
    //use this.repo as needed
  }    
}
Shyju
fuente
3
No dije que no funcionaría, también trabajé con el patrón de repositorio con EF, pero hoy me preguntaron por qué NO ES BUENO usar el patrón con DataBase, aplicación que usa Base de datos
2
Ok, como esta es la respuesta más popular, la elegiré como Respuesta correcta
65
¿Cuándo fue la última vez que el más popular == correcto?
HDave
14
DbContext ya es un repositorio, el repositorio está destinado a ser una abstracción de bajo nivel. Si desea abstraer diferentes fuentes de datos, cree objetos para representarlos.
Daniel Little
77
ColacX. Intentamos justamente eso, DBcontext justo en la capa del controlador, y estamos volviendo al patrón de repositorio. Con el patrón Repo, las pruebas unitarias pasaron de una burla masiva de DbContext que fallaba constantemente. EF fue difícil de usar y frágil y costó horas de investigación para matices EF. Ahora tenemos pequeños simulacros simples del repositorio. El código es más limpio. La separación del trabajo es más clara. Ya no estoy de acuerdo con la multitud de que EF ya es un patrón de repos y ya es comprobable por la unidad.
Rhyous
435

¿La mejor razón para no usar el patrón de repositorio con Entity Framework? Entity Framework ya implementa un patrón de repositorio. DbContextes su UoW (Unidad de trabajo) y cada uno DbSetes el repositorio. Implementar otra capa además de esto no solo es redundante, sino que dificulta el mantenimiento.

Las personas siguen patrones sin darse cuenta del propósito del patrón. En el caso del patrón de repositorio, el propósito es abstraer la lógica de consulta de la base de datos de bajo nivel. En los viejos tiempos de escribir declaraciones SQL en su código, el patrón de repositorio era una forma de mover ese SQL fuera de los métodos individuales dispersos en su base de código y localizarlo en un solo lugar. Tener un ORM como Entity Framework, NHibernate, etc. es un reemplazo para esta abstracción de código y, como tal, niega la necesidad del patrón.

Sin embargo, no es una mala idea crear una abstracción sobre su ORM, simplemente no es tan complejo como UoW / repositorio. Iría con un patrón de servicio, donde construyes una API que tu aplicación puede usar sin saber o sin importar si los datos provienen de Entity Framework, NHibernate o una API web. Esto es mucho más simple, ya que simplemente agrega métodos a su clase de servicio para devolver los datos que su aplicación necesita. Si estaba escribiendo una aplicación de tareas pendientes, por ejemplo, podría recibir una llamada de servicio para devolver los artículos que vencen esta semana y que aún no se han completado. Todo lo que su aplicación sabe es que si quiere esta información, llama a ese método. Dentro de ese método y en su servicio en general, interactúa con Entity Framework o cualquier otra cosa que esté utilizando. Luego, si luego decide cambiar ORM o extraer la información de una API web,

Puede parecer que ese es un argumento potencial para usar el patrón de repositorio, pero la diferencia clave aquí es que un servicio es una capa más delgada y está orientado a devolver datos completamente horneados, en lugar de algo en lo que continúe consultando, como con un repositorio.

Chris Pratt
fuente
68
Esta parece ser la única respuesta correcta.
Mike Chamberlain
10
Usted puede burlarse DbContextde EF6 + (ver: msdn.microsoft.com/en-us/data/dn314429.aspx ). Incluso en versiones menores, puede utilizar una falsificación DbContextde clase -como con burlaron DbSets, ya que DbSetimplementa un iterface, IDbSet.
Chris Pratt
14
@TheZenker, es posible que no haya seguido exactamente el patrón del repositorio. La diferencia más estricta es el valor de retorno. Los repositorios devuelven consultables, mientras que los servicios deben devolver enumerables. Incluso eso no es realmente tan blanco y negro, ya que hay cierta superposición allí. Se trata más de cómo lo usas. Un repositorio solo debe devolver el conjunto de todos los objetos, en los que luego debe realizar más consultas, mientras que el servicio debe devolver el conjunto de datos final y no debe admitir consultas adicionales.
Chris Pratt
10
A riesgo de sonar egoísta: están equivocados. Ahora, en lo que respecta a los tutoriales oficiales, Microsoft ha retrocedido utilizando repositorios de lo que he visto desde EF6. Con respecto al libro, no puedo hablar de por qué el autor eligió usar repositorios. Con lo que puedo hablar, como alguien en las trincheras que construyen aplicaciones a gran escala, es que usar el patrón de repositorio con Entity Framework es una pesadilla de mantenimiento. Una vez que se muda a algo más complejo que un puñado de repositorios, termina gastando cantidades exorbitantes de tiempo administrando sus repositorios / unidad de trabajo.
Chris Pratt
66
Por lo general, solo tengo un servicio por base de datos o método de acceso. Utilizo métodos genéricos para consultar varios tipos de entidades desde el mismo conjunto de métodos. Uso Ninject para inyectar mi contexto en mi servicio y luego mi servicio en mis controladores para que todo esté limpio y ordenado.
Chris Pratt
45

Aquí hay una toma de Ayende Rahien: Arquitectura en el pozo de la fatalidad: los males de la capa de abstracción del repositorio

Todavía no estoy seguro si estoy de acuerdo con su conclusión. Es un catch-22: por un lado, si envuelvo mi contexto de EF en repositorios específicos de tipo con métodos de recuperación de datos específicos de consulta, en realidad puedo probar mi código (más o menos), lo que es casi imposible con Entity Marco solo. Por otro lado, pierdo la capacidad de realizar consultas ricas y el mantenimiento semántico de las relaciones (pero incluso cuando tengo acceso completo a esas características siempre siento que estoy caminando sobre cáscaras de huevo alrededor de EF o cualquier otro ORM que pueda elegir , ya que nunca sé qué métodos podría admitir o no su implementación IQueryable, si interpretará mi adición a una colección de propiedades de navegación como una creación o simplemente una asociación, si va a cargar de manera lenta o ansiosa o no cargar por completo por defecto, etc. entonces quizás esto sea para mejor. El "mapeo" relacional de objetos de impedancia cero es algo de criatura mitológica, tal vez por eso la última versión de Entity Framework se denominó en código "Magic Unicorn").

Sin embargo, recuperar sus entidades a través de métodos de recuperación de datos específicos de consultas significa que sus pruebas unitarias ahora son esencialmente pruebas de caja blanca y no tiene otra opción en este asunto, ya que debe saber de antemano exactamente qué método de depósito va a utilizar la unidad bajo prueba. llamar para burlarse de él. Y todavía no está probando las consultas, a menos que también escriba pruebas de integración.

Estos son problemas complejos que necesitan una solución compleja. No puede solucionarlo simplemente pretendiendo que todas sus entidades son tipos separados sin relaciones entre ellas y las atomizan en su propio repositorio. Bueno, puedes , pero apesta.

Actualización: He tenido cierto éxito al usar el proveedor Effort para Entity Framework. Effort es un proveedor en memoria (código abierto) que le permite usar EF en las pruebas exactamente de la misma manera que lo haría en una base de datos real. Estoy considerando cambiar todas las pruebas en este proyecto. Estoy trabajando para usar este proveedor, ya que parece facilitar mucho las cosas. Es la única solución que he encontrado hasta ahora que aborda todos los problemas sobre los que estaba despotricando anteriormente. Lo único es que hay un ligero retraso al comenzar mis pruebas, ya que está creando la base de datos en memoria (usa otro paquete llamado NMemory para hacer esto), pero no veo esto como un problema real. Hay un artículo de Code Project que habla sobre el uso de Effort (versus SQL CE) para las pruebas.

luksan
fuente
3
Cualquier artículo de arquitectura sin mencionar la prueba de unidad se envía automáticamente a la papelera por mí. Uno de los puntos del patrón de repositorio es ganar algo de capacidad de prueba.
Sleeper Smith
3
Todavía puede tener pruebas unitarias sin envolver el contexto EF (que ya es un repositorio). Debe realizar pruebas unitarias de su dominio / servicios, no consultas de la base de datos (son pruebas de integración).
Daniel Little
2
La capacidad de prueba de EF ha mejorado mucho en la versión 6. Ahora puede burlarse completamente DbContext. De todos modos, siempre puedes burlarte DbSet, y esa es la carne de Entity Framework, de todos modos. DbContextes poco más que una clase para albergar sus DbSetpropiedades (repositorios) en una ubicación (unidad de trabajo), especialmente en un contexto de prueba de unidad, donde de todos modos no se necesita ni necesita toda la inicialización de la base de datos y cosas de conexión.
Chris Pratt
Perder la navegación de la entidad relacionada es malo y es contrario a OOP, pero tendrá más control sobre lo que se está consultando.
Alireza
Hasta el punto de prueba, EF Core ha recorrido un largo camino con los proveedores In-Memory e In-Memory listos para usar con Sqlite para permitir las pruebas unitarias. Traiga docker cuando necesite pruebas de integración para ejecutar pruebas en una base de datos en contenedor.
Sudhanshu Mishra
16

La razón por la que probablemente harías eso es porque es un poco redundante. Entity Framework le brinda una gran cantidad de ventajas funcionales y de codificación, es por eso que lo usa, si luego lo toma y lo envuelve en un patrón de repositorio, está descartando esas ventajas, también podría estar usando cualquier otra capa de acceso a datos.

Nueve colas
fuente
¿podría decirnos algunas de las ventajas de "Entity Framework le ofrece una gran cantidad de codificación y ventajas funcionales"?
ManirajSS
2
Esto es lo que quiso decir. var id = Entity.Where (i => i.Id == 1337) .Single () encapsulado y envuelto en un repositorio que básicamente no puede hacer una lógica de consulta como esta desde el exterior, lo que lo obliga a A agregar más código a el repositorio y la interfaz para buscar id. B devuelve el contexto de la entidad desde el repositorio para que pueda escribir la lógica de consulta (lo cual es una tontería)
ColacX
14

En teoría, creo que tiene sentido encapsular la lógica de conexión de la base de datos para que sea más fácil de reutilizar, pero como el siguiente enlace argumenta, nuestros marcos modernos esencialmente se ocupan de esto ahora.

Reconsiderando el patrón de repositorio

Esplendor
fuente
Me gustó el artículo, pero en mi humilde opinión para las aplicaciones empresariales, la capa de abstracción entre DAL y Bl DEBE tener función, ya que no podía saber qué se usará exactamente mañana. Pero gracias por compartir el enlace
1
Aunque personalmente creo que es cierto, por ejemplo, para NHibernate ( ISessionFactoryy ISessionson fácilmente burlables) DbContext, desafortunadamente no es tan fácil ...
Patryk Ćwiek
6

Una muy buena razón para usar el patrón de repositorio es permitir la separación de su lógica comercial y / o su UI de System.Data.Entity. Hay numerosas ventajas en esto, incluidos los beneficios reales en las pruebas unitarias al permitirle usar falsificaciones o simulacros.

James Culshaw
fuente
Estoy de acuerdo con esta respuesta Mis repositorios son básicamente métodos de extensión, que no hacen más que construir árboles de expresión. Sobre una abstracción MUY simple que simplemente proporciona funcionalidad genérica directamente sobre la parte superior de dbcontext. El único propósito real de la abstracción es hacer que IoC sea un poco más fácil. Creo que las personas intentan hacer cosas en sus repositorios que no deberían hacer. Hacen un repositorio por entidad, o ponen lógica de negocios allí que debería estar en la capa de servicios. Solo necesitas un repositorio genérico simple. No es necesario, solo proporciona una interfaz consistente.
Brandon
Una cosa más que solo quería agregar. Sí, CQRS es una metodología muy superior en la mayoría de los casos. Para algunos clientes en los que he trabajado cuando los chicos de la base de datos no funcionan bien con los desarrolladores (lo que ocurre con más frecuencia de lo que uno pensaría especialmente en los bancos), EF sobre SQL es la mejor opción. En ese escenario específico, cuando no tiene absolutamente ningún control sobre su base de datos, el patrón del repositorio tiene sentido. Porque se parece mucho a la estructura de datos y es fácil de traducir lo que sucede a la base de datos y viceversa. Es realmente una decisión política y logística en mi opinión. Para apaciguar a los dioses DB.
Brandon
1
De hecho, estoy empezando a cuestionar mis opiniones anteriores sobre esto. EF es un patrón combinado de Unidad de Trabajo y Repositorio. Como Chris Pratt mencionó anteriormente con EF6, puede burlarse fácilmente de los objetos Context y DbSet. Todavía creo que el acceso a los datos debe estar envuelto en clases para proteger las clases de lógica de negocios del mecanismo de acceso a datos real, pero ir a por completo y envolver EF con otro repositorio y la abstracción de la Unidad de Trabajo parece ser excesivo.
James Culshaw
No creo que esta sea una buena respuesta porque su declaración de apoyo es solo que hay numerosas ventajas mientras solo enumera una. La lista que hace no es una buena razón porque puede usar una base de datos en memoria para que la entidad lo haga examen de la unidad.
Joel McBeth
@jcmcbeth si miras mi comentario directamente encima de tu, verás que he cambiado mi opinión original con respecto al patrón de repositorio y EF.
James Culshaw
0

Hemos tenido problemas con instancias duplicadas pero diferentes de Entity Framework DbContext cuando un contenedor de IoC que repositorios nuevos () hasta por tipo (por ejemplo, un UserRepository y una instancia GroupRepository que cada uno llama a su propio IDbSet desde DBContext), a veces puede causar múltiples contextos por solicitud (en un contexto MVC / web).

La mayoría de las veces todavía funciona, pero cuando agrega una capa de servicio además de eso y esos servicios asumen que los objetos creados con un contexto se adjuntarán correctamente como colecciones secundarias a un nuevo objeto en otro contexto, a veces falla y otras no. t dependiendo de la velocidad de los commits.

Mufasa
fuente
He encontrado este problema en varios proyectos diferentes.
ColacX
0

Después de probar el patrón de repositorio en un proyecto pequeño, le recomiendo encarecidamente que no lo use; ¡no porque complique su sistema, y ​​no porque burlarse de datos sea una pesadilla, sino porque sus pruebas se vuelven inútiles!

La burla de datos le permite agregar detalles sin encabezados, agregar registros que violen las restricciones de la base de datos y eliminar entidades que la base de datos se negaría a eliminar. En el mundo real, una única actualización puede afectar múltiples tablas, registros, historial, resúmenes, etc., así como columnas como el campo de la última fecha de modificación, claves generadas automáticamente, campos calculados.

En resumen, su prueba en una base de datos real le brinda resultados reales y puede probar no solo sus servicios e interfaces, sino también el comportamiento de la base de datos. ¡Puede verificar si sus procedimientos almacenados hacen lo correcto con los datos, devuelven el resultado esperado o si el registro que envió para eliminar realmente se eliminó! Dichas pruebas también pueden exponer problemas como olvidarse de generar errores del procedimiento almacenado y miles de tales escenarios.

Creo que Entity Framework implementa un patrón de repositorio mejor que cualquiera de los artículos que he leído hasta ahora y va mucho más allá de lo que están tratando de lograr.

¡El repositorio era la mejor práctica en aquellos días en los que estábamos usando XBase, AdoX y Ado.Net, pero con entidad! (Repositorio sobre repositorio)

Por último, creo que muchas personas invierten mucho tiempo en aprender e implementar el patrón de repositorio y se niegan a dejarlo ir. Principalmente para probarse a sí mismos que no perdieron el tiempo.

Zawer Haroon
fuente
1
Excepto que NO desea probar el comportamiento de su base de datos en pruebas unitarias, ya que no es completamente ese nivel de prueba.
Mariusz Jamro
Sí, de lo que estás hablando aquí es de las pruebas de integración , y de hecho es valioso, pero las pruebas unitarias son totalmente diferentes. Las pruebas unitarias nunca deberían llegar a una base de datos real, pero se le recomienda agregar pruebas de integración que sí lo hagan.
Chris Pratt
-3

Se debe a migraciones: no es posible hacer que las migraciones funcionen, ya que la cadena de conexión reside en web.config. Pero, el DbContext reside en la capa del repositorio. IDbContextFactory necesita tener una cadena de configuración en la base de datos. Pero no hay forma de que las migraciones obtengan la cadena de conexión de web.config.

Hay soluciones, ¡pero todavía no he encontrado una solución limpia para esto!

Trueno
fuente