NO use el patrón de repositorio, use el ORM como está (EF)


95

Siempre usé el patrón Repository pero para mi último proyecto quería ver si podía perfeccionar el uso de él y mi implementación de “Unit Of Work”. Cuanto más comencé a excavar, comencé a hacerme la pregunta: "¿Realmente lo necesito?"

Ahora, todo esto comienza con un par de comentarios en Stackoverflow con un rastro a la publicación de Ayende Rahien en su blog, con 2 específicos,

Probablemente se podría hablar de esto por siempre jamás y depende de diferentes aplicaciones. Lo que me gusta saber

  1. ¿Sería este enfoque adecuado para un proyecto de Entity Framework?
  2. Al utilizar este enfoque, ¿la lógica empresarial sigue estando en una capa de servicio o métodos de extensión (como se explica a continuación, lo sé, el método de extensión está utilizando la sesión NHib)?

Eso se hace fácilmente usando métodos de extensión. Limpio, sencillo y reutilizable.

public static IEnumerable GetAll(
    this ISession instance, Expression<Func<T, bool>> where) where T : class
{
    return instance.QueryOver().Where(where).List();
}

Usando este enfoque y Ninjectcomo DI, ¿necesito crear Contextuna interfaz e inyectarla en mis controladores?

Respuestas:


103

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 DbContextuso de uno IDbContextu 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.


17
No crea repositorios para poder realizar pruebas unitarias. Cree repositorios para poder realizar pruebas unitarias de la lógica empresarial . En cuanto a asegurarse de que las consultas funcionen: es mucho más fácil escribir pruebas de integración para repositorios, ya que solo contienen lógica y no negocios.
jgauffin

16
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.
jgauffin

2
Todos estos son puntos muy válidos con una gran cantidad de verdad detrás de ellos. Sin embargo, lo que falta es la comprensión de que LINQ esparcido sobre una aplicación en lugar de restringido a una ubicación consistente crea el equivalente EF de las llamadas SQL en las páginas de código subyacente. Cada consulta LINQ es un punto de mantenimiento potencial en una aplicación, y cuanto más hay (y más extendidos están), mayores son los costos y riesgos de mantenimiento. Imagine agregar una bandera 'eliminada' a una entidad y tener que ubicar cada lugar en una aplicación grande en la que se consulta la entidad, tener que modificar cada una ...
DVK

2
Creo que esto es miope y hastiado. ¿Por qué filtrarías lógica en el repositorio? Y si lo hicieras, ¿por qué importaría? Es una implementación de datos. Todo lo que estamos haciendo es aislar el LINQ del resto del código ocultándolo detrás del repositorio. Dice que no lo pruebe, pero luego usa el no poder probarlo como un argumento en contra de hacerlo. Así que cree el repositorio, no exponga IQueryable y no lo pruebe. Al menos puede probar todo lo demás de forma aislada de la implementación de datos. Y esa probabilidad del 1% de un cambio de base de datos sigue siendo enorme.
Sinaesthetic

5
+1 para esta respuesta. Encuentro que realmente NO necesitamos repositorios con Entity Framework Core. El DbSetes el repositorio y DbContextes 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 a InMemory. ¡Y haz tus pruebas! Está bien documentado en MSDN.
Mohammed Noureldin

49

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

Para el tipo de entidad única con más de 20 campos, ¿cómo diseñará el método de consulta para admitir cualquier combinación de permutación? No desea limitar la búsqueda solo por nombre, ¿qué pasa con la búsqueda con propiedades de navegación, enumerar todos los pedidos con artículo con código de precio específico, 3 niveles de búsqueda de propiedad de navegación? La única razón por la que IQueryablese inventó fue poder componer cualquier combinación de búsqueda en base de datos. Todo se ve muy bien en teoría, pero la necesidad del usuario gana por encima de la teoría.

Nuevamente: una entidad con más de 20 campos está modelada incorrectamente. Es una entidad de DIOS. Descomponerlo.

No estoy argumentando que IQueryableno 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 IQueryableen 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).


6
No exponer IQueryable conduce a búsquedas limitadas, y las personas terminan creando más métodos Get para diferentes tipos de consultas y, finalmente, hace que el repositorio sea más complejo.
Akash Kava

3
no ha abordado el problema principal en absoluto: exponer IQueryable a través de un repositorio no es una abstracción completa.
jgauffin

1
Tener un objeto de consulta que contenga toda la infraestructura necesaria para ejecutarse en sí mismo es el camino a seguir, en mi opinión. Le da los campos que son los términos de búsqueda y devuelve una lista de resultados. Dentro del QO, puede hacer lo que quiera. Y es una interfaz tan fácil de probar. Vea mi publicación arriba. Es lo mejor.
h.alex

2
Personalmente, creo que también tiene sentido implementar la interfaz IQueryable <T> en una clase Repository, en lugar de exponer el conjunto subyacente en uno de sus miembros.
dark_perfect

3
@yat: un repositorio por raíz agregada. Pero en mi humilde opinión no es raíz agregada y agregada de tablas, sino solo raíz agregada y agregados . El almacenamiento real puede usar solo una tabla o muchas de ellas, es decir, puede que no sea un mapeo uno a uno entre cada agregado y una tabla. Utilizo repositorios para reducir la complejidad y eliminar cualquier dependencia del almacenamiento subyacente.
jgauffin

27

En mi opinión, tanto la Repositoryabstracción como la UnitOfWorkabstracció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, DbContextes el UnitOfWorky DbSetes el Repository. Por lo general no necesita prueba de la unidad del UnitOfWorko de Repositoryellos 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 UnitOfWorky Repositoryle 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:

public interface IRepository<T>
    where T : class
{
    T Add(T entity);
    void Delete(T entity);
    IQueryable<T> AsQueryable();
}

Y su implementación:

public class Repository<T> : IRepository<T>
    where T : class
{
    private readonly IDbSet<T> _dbSet;
    public Repository(PPContext context) 
    {
        _dbSet = context.Set<T>();
    }

    public T Add(T entity)
    { 
        return _dbSet.Add(entity); 
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity); 
    }

    public IQueryable<T> AsQueryable() 
    {
        return _dbSet.AsQueryable();
    }
}

Nada fuera de lo común hasta ahora, pero ahora queremos agregar algunos registros, fácil con un Decorador de registros .

public class RepositoryLoggerDecorator<T> : IRepository<T>
    where T : class
{
    Logger logger = LogManager.GetCurrentClassLogger();
    private readonly IRepository<T> _decorated;
    public RepositoryLoggerDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
        T added = _decorated.Add(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        return added;
    }

    public void Delete(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        _decorated.Delete(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable();
    }
}

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 IRepositoryabstracció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 Repositoryabstracción, la respuesta es "es fácil agregar un decorador"

public class RepositoryTennantFilterDecorator<T> : IRepository<T>
    where T : class
{
    //public for Unit Test example
    public readonly IRepository<T> _decorated;
    public RepositoryTennantFilterDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        return _decorated.Add(entity);
    }

    public void Delete(T entity)
    {
        _decorated.Delete(entity);
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable().Where(o => true);
    }
}

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 .

[TestFixture]
public class IRepositoryTesting
{
    [Test]
    public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
    {
        Container container = new Container();
        container.RegisterLifetimeScope<PPContext>();
        container.RegisterOpenGeneric(
            typeof(IRepository<>), 
            typeof(Repository<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryLoggerDecorator<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryTennantFilterDecorator<>));
        container.Verify();

        using (container.BeginLifetimeScope())
        {
            var result = container.GetInstance<IRepository<Image>>();

            Assert.That(
                result, 
                Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
            Assert.That(
                (result as RepositoryTennantFilterDecorator<Image>)._decorated,
                Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
        }
    }
}

11

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.

public class DefaultSecurityContext : BaseSecurityContext {

  public static DefaultSecurityContext Instance = new DefaultSecurityContext();

  // UserID for currently logged in User
  public static long UserID{
       get{
             return long.Parse( HttpContext.Current.User.Identity.Name );
       }
  }

  public DefaultSecurityContext(){
  }

  protected override void OnCreate(){

        // User can access his own Account only
        var acc = CreateRules<Account>();

        acc.SetRead( y => x=> x.AccountID == UserID ) ;
        acc.SetWrite( y => x=> x.AccountID == UserID );

        // User can only modify AccountName and EmailAddress fields
        acc.SetProperties( SecurityRules.ReadWrite, 
              x => x.AccountName,
              x => x.EmailAddress);

        // User can read AccountType field
        acc.SetProperties<Account>( SecurityRules.Read, 
              x => x.AccountType);

        // User can access his own Orders only
        var order = CreateRules<Order>();
        order.SetRead( y => x => x.CustomerID == UserID );

        // User can modify Order only if OrderStatus is not complete
        order.SetWrite( y => x => x.CustomerID == UserID 
            && x.OrderStatus != "Complete" );

        // User can only modify OrderNotes and OrderStatus
        order.SetProperties( SecurityRules.ReadWrite, 
              x => x.OrderNotes,
              x => x.OrderStatus );

        // User can not delete orders
        order.SetDelete(order.NotSupportedRule);
  }
}

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.


3
La prueba unitaria contra una base de datos significa que tiene requisitos externos adicionales para sus pruebas. Si esa base de datos está inactiva, o los datos se borran, o si algo le sucede a esa base de datos, sus pruebas fallarán. Esto no es deseado. Los repositorios que exponen IQueryable tardan unos 2 minutos en configurarse. Aquí no hay pérdida de tiempo. ¿Por qué DI te tomó tanto tiempo? Todo esto toma minutos. Diré que todo esto funcionó muy bien para las pruebas unitarias de mis consultas complejas en mi capa de servicio. Fue tan agradable no necesitar una base de datos para conectarse. Obtener un marco de burla de nuget tomó alrededor de un minuto. Estas cosas no toman tiempo.
user441521

@ user441521 ¿Repositorios con IQueryable 2 minutos para configurar? en qué mundo vive, cada solicitud de asp.net en nuestro sitio en vivo se atiende en milisegundos. Burlarse y falsificar, etc., agrega más complejidad al código, es una pérdida total de tiempo. Las pruebas unitarias son inútiles cuando la unidad no se define como unidad lógica empresarial.
Akash Kava

7

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.


Entonces, los métodos de extensión, ¿qué tan extensos son? Digamos que necesito obtener el estado de otra entidad en mi extensión. Esa es mi mayor preocupación en este momento. ¿Le importaría mostrar algunos ejemplos de métodos de extensión?
Dejan.S

ayende.com/blog/153473/… y ayende.com/blog/153569/… . (Estas son revisiones de una arquitectura (¿Framework?) Llamada s # arp lite. En su mayoría buena, pero no está de acuerdo con los repositorios y las abstracciones de CUD).
Josh

Su base NHibernate. ¿No tienes ningún ejemplo con EF? Y de nuevo, cuando necesito llamar a otra entidad, ¿cómo se hace mejor en el método de extensión estática?
Dejan.S

3
Todo esto está muy bien hasta que una propiedad de su objeto de dominio necesita ser hidratada por datos que no están almacenados en su base de datos; o necesita pasar a una tecnología que sea más eficiente que su ORM hinchado. OOPS! Un ORM simplemente NO es un reemplazo de un repositorio, es un detalle de implementación de uno.
cdaq

2

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.


1

Linq es un 'Repositorio' hoy en día.

ISession + Linq ya es el repositorio y no necesita GetXByYmétodos ni QueryData(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).

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}

¿Qué haces para la capa de servicio?
Dejan.S

Los controladores consultan el repositorio para datos de solo lectura, ¿por qué agregar una capa adicional? La otra posibilidad es utilizar "ContentService", que cada vez más tiende a ser un repositorio de nivel de servicio: GetXByY, etc., etc. Para operaciones de modificación, los servicios de aplicaciones son solo abstracciones sobre casos de uso, usan BL y repositorios libremente ..
mikalai

Estoy acostumbrado a hacer la capa de servicio para la lógica empresarial. No estoy realmente seguro de seguir lo que dices con ContentService, por favor da más detalles. ¿Sería una mala práctica hacer clases de ayuda como "capa de servicio"?
Dejan.S

Por "capa de servicio" me refiero a "servicios de aplicación". Pueden usar el repositorio y cualquier otra parte pública de la capa de dominio. La "capa de servicio" no es una mala práctica, pero evitaría hacer la clase XService solo para proporcionar el resultado List <X>. El campo de comentario parece ser demasiado corto para describir los servicios en detalle, lo siento.
mikalai

¿Qué pasa si, digamos un cálculo de carrito y necesita obtener parámetros de configuración de la aplicación y parámetros específicos del cliente para hacer un cálculo, y esto se reutiliza en varios lugares de la aplicación? ¿Cómo manejas esa situación? clase de ayuda o servicio de aplicación?
Dejan.S

1

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

public interface IRepository : IDisposable
{
    void Save<TEntity>(TEntity entity);
    void SaveList<TEntity>(IEnumerable<TEntity> entities);

    void Delete<TEntity>(TEntity entity);
    void DeleteList<TEntity>(IEnumerable<TEntity> entities);

    IList<TEntity> GetAll<TEntity>() where TEntity : class;
    int GetCount<TEntity>() where TEntity : class;

    void StartConversation();
    void EndConversation();

    //if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
    TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}

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

public interface IQueryObject<TResult>
{
    /// <summary>Provides configuration options.</summary>
    /// <remarks>
    /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository.
    /// If not used through a repository, it can be useful as a configuration option.
    /// </remarks>
    void Configure(object parameter);

    /// <summary>Implementation of the query.</summary>
    TResult GetResult();
}

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)

public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>>
    where TEntity : class
{
    public override IList<TEntity> GetResult()
    {
        return this.Session.CreateCriteria<TEntity>().List<TEntity>();
    }
}

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.


1
Las entidades de persistencia (sus POCO) NO son entidades comerciales / de dominio. Y el propósito del repositorio es desacoplar la capa empresarial (o cualquier) de la persistencia.
MikeSW

No veo el acoplamiento. Algo de acuerdo en la parte de POCO, pero no me importa. Nada que le impida tener POCO "genuinos" y seguir utilizando este enfoque.
h.alex

1
Las entidades no tienen que ser POCO tontas en absoluto. De hecho, modelar la lógica empresarial en Entidades es lo que hace todo el tiempo la multitud de DDD. Este estilo de desarrollo combina muy bien con NH o EF.
chris

1

Para mí, es una decisión simple, con relativamente pocos factores. Los factores son:

  1. Los repositorios son para clases de dominio.
  2. En algunas de mis aplicaciones, las clases de dominio son las mismas que mis clases de persistencia (DAL), en otras no.
  3. Cuando son iguales, EF ya me está proporcionando repositorios.
  4. EF proporciona carga diferida e IQueryable. Me gustan estos.
  5. Abstract / 'facading' / reimplementar el repositorio sobre EF generalmente significa pérdida de perezoso e IQueryable

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.

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.