Estaba leyendo algunos artículos sobre las ventajas de crear repositorios genéricos para una nueva aplicación ( ejemplo ). La idea parece agradable porque me permite usar el mismo repositorio para hacer varias cosas para varios tipos de entidades diferentes a la vez:
IRepository repo = new EfRepository(); // Would normally pass through IOC into constructor
var c1 = new Country() { Name = "United States", CountryCode = "US" };
var c2 = new Country() { Name = "Canada", CountryCode = "CA" };
var c3 = new Country() { Name = "Mexico", CountryCode = "MX" };
var p1 = new Province() { Country = c1, Name = "Alabama", Abbreviation = "AL" };
var p2 = new Province() { Country = c1, Name = "Alaska", Abbreviation = "AK" };
var p3 = new Province() { Country = c2, Name = "Alberta", Abbreviation = "AB" };
repo.Add<Country>(c1);
repo.Add<Country>(c2);
repo.Add<Country>(c3);
repo.Add<Province>(p1);
repo.Add<Province>(p2);
repo.Add<Province>(p3);
repo.Save();
Sin embargo, el resto de la implementación del Repositorio depende mucho de Linq:
IQueryable<T> Query();
IList<T> Find(Expression<Func<T,bool>> predicate);
T Get(Expression<Func<T,bool>> predicate);
T First(Expression<Func<T,bool>> predicate);
//... and so on
Este patrón de repositorio funcionó de maravilla para Entity Framework, y prácticamente ofreció una asignación 1 a 1 de los métodos disponibles en DbContext / DbSet. Pero dada la lenta incorporación de Linq en otras tecnologías de acceso a datos fuera de Entity Framework, ¿qué ventaja ofrece esto sobre trabajar directamente con DbContext?
Intenté escribir una versión de PetaPoco del Repositorio, pero PetaPoco no es compatible con Expresiones de Linq, lo que hace que crear una interfaz genérica de IRepository sea bastante inútil a menos que solo lo use para GetAll, GetById, Add, Update, Delete y Save básicos. métodos y utilizarlo como una clase base. Luego, debe crear repositorios específicos con métodos especializados para manejar todas las cláusulas "where" que anteriormente podría pasar como predicado.
¿El patrón de repositorio genérico es útil para algo fuera de Entity Framework? Si no, ¿por qué alguien lo usaría en lugar de trabajar directamente con Entity Framework?
El enlace original no refleja el patrón que estaba usando en mi código de muestra. Aquí hay un ( enlace actualizado ).
Respuestas:
El repositorio genérico es incluso inútil (y en mi humilde opinión también es malo) para Entity Framework. No aporta ningún valor adicional a lo que ya proporciona
IDbSet<T>
(que es por cierto repositorio genérico).Como ya ha encontrado, el argumento de que el repositorio genérico puede ser reemplazado por la implementación de otra tecnología de acceso a datos es bastante débil porque puede exigir la escritura de su propio proveedor de Linq.
El segundo argumento común acerca de las pruebas unitarias simplificadas también es incorrecto porque el depósito / conjunto burlón con almacenamiento de datos en memoria reemplaza al proveedor de Linq con otro que tiene capacidades diferentes. El proveedor de Linq a entidades solo admite un subconjunto de características de Linq; incluso no admite todos los métodos disponibles en la
IQueryable<T>
interfaz. Compartir árboles de expresión entre la capa de acceso a datos y las capas de lógica de negocios evita cualquier falsificación de la capa de acceso a datos: la lógica de consulta debe estar separada.Si desea tener una fuerte abstracción "genérica", también debe involucrar otros patrones. En este caso, debe utilizar un lenguaje de consulta abstracto que se pueda traducir por repositorio a un lenguaje de consulta específico compatible con la capa de acceso a datos utilizada. Esto se maneja por patrón de especificación. Linq on
IQueryable
es la especificación (pero la traducción requiere un proveedor, o algún visitante personalizado que traduzca el árbol de expresión en consulta), pero puede definir su propia versión simplificada y usarla. Por ejemplo, NHibernate usa Criteria API. Aún así, la forma más simple es usar un repositorio específico con métodos específicos. De esta forma, es la más sencilla de implementar, la más simple de probar y la más simple de falsificar en las pruebas unitarias porque la lógica de consulta está completamente oculta y separada detrás de la abstracción.fuente
ISession
que se puede burlar fácilmente para propósitos de prueba de unidad general, y con mucho gusto dejaría el 'repositorio' cuando trabaje con EF también, pero ¿hay alguna manera más fácil y directa de recrear eso? Algo parecidoISession
yISessionFactory
porque no hayIDbContext
hasta donde puedo decir ...IDbContext
, si lo deseaIDbContext
puede simplemente crear uno e implementarlo en su contexto derivado.El problema no es el patrón del repositorio. Tener una abstracción entre obtener datos y cómo los está obteniendo es algo bueno.
El problema aquí es la implementación. Asumir que una expresión arbitraria funcionará para el filtrado es incierto en el mejor de los casos.
Hacer que un repositorio funcione para todos sus objetos directamente pierde el sentido. Los objetos de datos rara vez se asignarán directamente a objetos comerciales. Pasar T para filtrar tiene mucho menos sentido en estas situaciones. Y proporcionar tanta funcionalidad garantiza que no podrá soportarlo una vez que aparezca un proveedor diferente.
fuente
El valor de una capa de datos genéricos (un repositorio es un tipo particular de capa de datos) permite que el código cambie el mecanismo de almacenamiento subyacente con poco o ningún impacto en el código de llamada.
En teoría, esto funciona bien. En la práctica, como has observado, la abstracción a menudo es permeable. Los mecanismos utilizados para acceder a los datos en uno son diferentes a los mecanismos en otro. En algunos casos, terminas escribiendo el código dos veces: una vez en la capa empresarial y repitiéndolo en la capa de datos.
La forma más efectiva de crear una capa de datos genéricos es conocer los diferentes tipos de fuentes de datos que la aplicación usará de antemano. Como ha visto, asumir que LINQ o SQL es universal puede ser un problema. Intentar actualizar nuevos almacenes de datos probablemente resulte en una reescritura.
[Editar: se agregó lo siguiente.]
También depende de lo que la aplicación necesite de la capa de datos. Si la aplicación solo carga o almacena objetos, la capa de datos puede ser muy simple. A medida que aumenta la necesidad de buscar, ordenar y filtrar, aumenta la complejidad de la capa de datos y las abstracciones comienzan a perderse (como exponer consultas LINQ en la pregunta). Sin embargo, una vez que los usuarios pueden proporcionar sus propias consultas, el costo / beneficio de la capa de datos debe sopesarse cuidadosamente.
fuente
Tener una capa de código sobre la base de datos vale la pena en casi todos los casos. En general, preferiría un patrón "GetByXXXXX" en dicho código: le permite optimizar las consultas detrás de él según sea necesario mientras mantiene la interfaz de usuario libre de desorden en la interfaz de datos.
Aprovechar los genéricos es definitivamente un juego justo: tener un
Load<T>(int id)
método tiene mucho sentido. Pero construir repositorios alrededor de LINQ es el equivalente de 2010 de soltar consultas sql en todas partes con un poco de seguridad de tipo agregado.fuente
Bueno, con el enlace provisto, puedo ver que puede ser un contenedor útil para un
DataServiceContext
, pero no reduce las manipulaciones de código ni mejora la legibilidad. Además, el acceso aDataServiceQuery<T>
está obstruido, lo que limita la flexibilidad a.Where()
y.Single()
. Tampoco seAddRange()
proporcionan o alternativas. Tampoco seDelete(Predicate)
proporciona lo que podría ser útil (repo.Delete<Person>( p => p.Name=="Joe" );
para eliminar Joe-s). Etc.Conclusión: dicha API obstruye la API nativa y la limita a unas pocas operaciones simples.
fuente
Yo diría que sí". Dependiendo de su modelo de datos, un repositorio genérico bien desarrollado puede hacer que el nivel de acceso a datos sea más ágil, ordenado y mucho más robusto.
Lea esta serie de artículos (por @ chris-pratt ):
fuente