¿Existe una ventaja real para el repositorio genérico?


28

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 ).



1
¿Se trata realmente de "ventajas del repositorio genérico" o se trata más de "cómo ocultar consultas complejas detrás de una interfaz de repositorio genérico"? ¿Es posible ser genérico si su interfaz y uso dependen de linq? Nuestro Repositoryapi tiene un método QueryByExample que es completamente independiente de la técnica de búsqueda y permitiría cambiar la implementación.
k3b

"Me permite usar el mismo repositorio para hacer varias cosas para varios tipos de entidades diferentes" => ¿Soy yo o simplemente entendiste mal qué es un repositorio genérico (incluso con respecto al artículo mencionado)? Para mí, el repositorio genérico siempre ha significado templating todos los repositorios con una sola clase o interfaz, no tener un único repositorio ejemplo, la gestión de la persistencia de todas las entidades independientemente de su tipo ...
guillaume31

Respuestas:


35

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 IQueryablees 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.


Solo una pregunta rápida, NHibernate tiene el ISessionque 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 parecido ISessiony ISessionFactoryporque no hay IDbContexthasta donde puedo decir ...
Patryk Ćwiek

No, no hay IDbContext, si lo desea IDbContextpuede simplemente crear uno e implementarlo en su contexto derivado.
Ladislav Mrnka

Entonces está diciendo que para las aplicaciones MVC cotidianas, IDbSet <T> debería proporcionar un repositorio genérico lo suficientemente bueno como para olvidarse por completo del patrón del repositorio. Excepto en situaciones especiales, por ejemplo, donde no se permite ninguna referencia DAL en su proyecto MVC.
ProfK

1
Buena respuesta. Algunos desarrolladores crean solo otra capa de abstracciones sin ningún motivo. OMI, EF no necesita un depósito genérico ni una unidad de trabajo (están incorporados)
ashraf

1
IDbSet <T> es un repositorio genérico con una dependencia del Entity Framework que de alguna manera anula el propósito de utilizar un repositorio genérico para abstraer la dependencia del Entity Framework.
Joel McBeth

7

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.


Entonces, ¿tiene más sentido tener un Repositorio por objeto de datos, o grupo de objetos estrechamente relacionados, con métodos específicos como GetById (int id), SortedList (), etc.? ¿Devuelvo listas de objetos de datos de un repositorio o las estoy transformando en objetos de negocios con los campos necesarios también? Un poco pensó que eso fue lo que sucedió en la capa de Servicio / Negocio.
Sam

1
@sam: tiendo a favorecer repositorios más específicos, sí. Si están haciendo la traducción o no, depende de dónde cambien las cosas. Si su dominio está bien definido, devolvería las cosas en la forma del dominio. Si los datos están bien definidos, devolvería cosas en la forma de las estructuras de datos. De lo contrario, tendría una entidad que sirve como la base bien definida para construir y luego adaptarme a eso.
Telastyn

1
No hay ninguna razón por la que no pueda tener repositorios específicos que deleguen las cosas comunes a un repositorio genérico, sino que también tengan métodos específicos de repositorio adicionales para llamadas más complejas. Lo he visto así varias veces.
Eric King

@EricKing también lo tengo, y fue bueno allí, pero tiende a filtrar algo de abstracción ya que las cosas comunes tienden a existir solo debido a lo común de cómo se almacenan los datos (GetByID, por ejemplo, requiere tablas con ID).
Telastyn

@Telastyn Sí, estaría de acuerdo con eso, pero también sucede con repositorios específicos. Las abstracciones con fugas no son específicas de los repositorios genéricos. (Wow, ¿podría haber dicho eso más torpemente?)
Eric King

2

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.


1

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.


0

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 a DataServiceQuery<T>está obstruido, lo que limita la flexibilidad a .Where()y .Single(). Tampoco se AddRange()proporcionan o alternativas. Tampoco se Delete(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.


Estás en lo correcto. Ese no era el artículo usando el patrón que tengo en mi código de muestra. Intentaré encontrar el enlace cuando llegue a casa.
Sam

Enlace actualizado agregado al final de la pregunta.
Sam

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.