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?
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?
Respuestas:
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 DbContext
con un repositorio es la capacidad de prueba unitaria . Puede tener su IRepository
interfaz con 2 implementaciones, una (el Repositorio real) que utiliza DbContext
para comunicarse con la base de datos y la segunda, FakeRepository
que puede devolver objetos en memoria / datos simulados. Esto hace que su IRepository
unidad 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
}
}
¿La mejor razón para no usar el patrón de repositorio con Entity Framework? Entity Framework ya implementa un patrón de repositorio. DbContext
es su UoW (Unidad de trabajo) y cada uno DbSet
es 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.
DbContext
de EF6 + (ver: msdn.microsoft.com/en-us/data/dn314429.aspx ). Incluso en versiones menores, puede utilizar una falsificación DbContext
de clase -como con burlaron DbSet
s, ya que DbSet
implementa un iterface, IDbSet
.
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.
DbContext
. De todos modos, siempre puedes burlarte DbSet
, y esa es la carne de Entity Framework, de todos modos. DbContext
es poco más que una clase para albergar sus DbSet
propiedades (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.
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.
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.
ISessionFactory
y ISession
son fácilmente burlables) DbContext
, desafortunadamente no es tan fácil ...
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.
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.
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.
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!