NÃO usando o padrão de repositório, use o ORM como está (EF)

95

Eu sempre usei o padrão Repository, mas para meu último projeto eu queria ver se eu poderia aperfeiçoar o uso dele e minha implementação de “Unit Of Work”. Quanto mais eu comecei a cavar, comecei a me perguntar: "Eu realmente preciso disso?"

Agora, tudo isso começa com alguns comentários no Stackoverflow com um rastreamento da postagem de Ayende Rahien em seu blog, com 2 específicos,

Isso provavelmente poderia ser falado para todo o sempre e depende de diferentes aplicações. O que eu gosto de saber,

  1. esta abordagem seria adequada para um projeto Entity Framework?
  2. usando essa abordagem, a lógica de negócios ainda está em uma camada de serviço ou métodos de extensão (conforme explicado abaixo, eu sei, o método de extensão está usando a sessão NHib)?

Isso é feito facilmente usando métodos de extensão. Limpo, simples e reutilizável.

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

Usando essa abordagem e Ninjectcomo DI, preciso fazer a Contextinterface a e injetá-la nos meus controladores?

Dejan.S
fonte

Respostas:

103

Já percorri muitos caminhos e criei muitas implementações de repositórios em diferentes projetos e ... Joguei a toalha e desisti, aqui está o porquê.

Codificação para a exceção

Você codifica para 1% de chance de seu banco de dados mudar de uma tecnologia para outra? Se você está pensando sobre o estado futuro de sua empresa e diz que sim, essa é uma possibilidade, então a) eles devem ter muito dinheiro para fazer uma migração para outra tecnologia de banco de dados ou b) você está escolhendo uma tecnologia de banco de dados para se divertir ou c ) algo deu terrivelmente errado com a primeira tecnologia que você decidiu usar.

Por que jogar fora a rica sintaxe LINQ?

LINQ e EF foram desenvolvidos para que você pudesse fazer coisas legais com ele para ler e percorrer gráficos de objetos. Criar e manter um repositório que possa lhe dar a mesma flexibilidade para fazer isso é uma tarefa monstruosa. Em minha experiência, sempre que criei um repositório, SEMPRE tive vazamento de lógica de negócios na camada do repositório para tornar as consultas mais eficientes e / ou reduzir o número de acessos ao banco de dados.

Não quero criar um método para cada permutação de uma consulta que tenho que escrever. Também posso escrever procedimentos armazenados. Eu não quero GetOrder, GetOrderWithOrderItem, GetOrderWithOrderItemWithOrderActivity, GetOrderByUserId, e assim por diante ... Eu só quero chegar a entidade principal e transversal e incluir o gráfico do objeto como eu por favor.

A maioria dos exemplos de repositórios são besteiras

A menos que você esteja desenvolvendo algo REALMENTE básico, como um blog ou algo, suas consultas nunca serão tão simples quanto 90% dos exemplos que você encontra na Internet em torno do padrão de repositório. Eu não posso enfatizar isso o suficiente! Isso é algo que é preciso rastejar na lama para descobrir. Sempre haverá aquela consulta que quebrará seu repositório / solução perfeitamente pensado que você criou, e não é até esse ponto em que você se questiona e começa a dívida / erosão técnica.

Não me teste de unidade mano

Mas e quanto ao teste de unidade se eu não tiver um repositório? Como vou zombar? Simples, você não precisa. Vamos olhar de ambos os ângulos:

Sem repositório - você pode simular o DbContextusando um IDbContextou alguns outros truques, mas então você está realmente testando a unidade LINQ to Objects e não LINQ to Entities porque a consulta é determinada em tempo de execução ... OK, isso não é bom! Portanto, agora cabe ao teste de integração cobrir isso.

Com repositório - agora você pode simular seus repositórios e testar a (s) camada (s) intermediária (s). Ótimo, certo? Bem, não realmente ... Nos casos acima em que você precisa vazar lógica na camada do repositório para tornar as consultas mais eficientes e / ou menos acessos ao banco de dados, como seus testes de unidade podem cobrir isso? Agora está na camada de repo e você não quer testar, IQueryable<T>certo? Também vamos ser honestos, seus testes de unidade não vão cobrir as consultas que têm uma .Where()cláusula de 20 linhas e.Include()é um monte de relacionamentos e acessa o banco de dados novamente para fazer todas essas outras coisas, blá, blá, blá de qualquer maneira, porque a consulta é gerada em tempo de execução. Além disso, como você criou um repositório para manter a persistência das camadas superiores ignorante, se agora você deseja alterar a tecnologia de seu banco de dados, desculpe seus testes de unidade definitivamente não vão garantir os mesmos resultados em tempo de execução, voltando aos testes de integração. Então, todo o ponto do repositório parece estranho ..

2 centavos

Já perdemos muita funcionalidade e sintaxe ao usar EF em vez de procedimentos armazenados simples (inserções em massa, exclusões em massa, CTEs, etc.), mas também codifico em C #, então não preciso digitar binário. Usamos EF para termos a possibilidade de usar diferentes provedores e trabalhar com gráficos de objetos de uma forma bem relacionada entre muitas coisas. Certas abstrações são úteis e outras não.

Ryan
fonte
16
Você não cria repositórios para poder testá-los por unidade. Você cria repositórios para poder testar a lógica de negócios de unidade . Quanto a garantir que as consultas funcionem: é muito mais fácil escrever testes de integração para repositórios porque eles contêm apenas lógica e não qualquer negócio.
jgauffin
16
Coding for the exception: Usar repositórios não significa mudar o mecanismo de banco de dados. É separar o negócio da persistência.
jgauffin
2
Esses são todos pontos muito válidos com uma grande dose de verdade por trás deles. O que está faltando, entretanto, é a percepção de que LINQ espalhado sobre um aplicativo em vez de restrito a um local consistente cria o equivalente EF de chamadas SQL em páginas codebehind. Cada consulta LINQ é um ponto de manutenção potencial em um aplicativo e, quanto mais houver (e mais difundidos), maiores serão os custos e riscos de manutenção. Imagine adicionar um sinalizador 'excluído' a uma entidade e ter que localizar cada lugar em um grande aplicativo que a entidade é consultada, tendo que modificar cada ...
DVK
2
Acho que isso é míope e enfadonho. Por que você vazaria lógica no repo? E se você fez, por que isso importa? É uma implementação de dados. Tudo o que estamos fazendo é isolar o LINQ do resto do código, ocultando-o atrás do repo. Você diz para não testar, mas então usa o fato de não poder testar como um argumento contra fazê-lo. Portanto, faça o repo, não exponha IQueryable e não o teste. Pelo menos você pode testar todo o resto isoladamente da implementação de dados. E essa chance de 1% de uma mudança de banco de dados ainda é enorme, $ a menos.
Sinestésico
5
1 para esta resposta. Acho que realmente NÃO precisamos de repositórios com o Entity Framework Core. O DbSeté o repositório e DbContexté a Unidade de Trabalho . Por que implementar o padrão de repositório quando o ORM já faz isso por nós! Para testar, basta alterar o provedor para InMemory. E faça seus testes! Está bem documentado no MSDN.
Mohammed Noureldin
49

O padrão do repositório é uma abstração . Seu objetivo é reduzir a complexidade e tornar o resto do código persistente e ignorante. Como bônus, ele permite que você escreva testes de unidade em vez de testes de integração .

O problema é que muitos desenvolvedores não conseguem entender o propósito dos padrões e criam repositórios que vazam informações específicas de persistência até o chamador (normalmente por exposição IQueryable<T>). Ao fazer isso, eles não obtêm nenhum benefício ao usar o OR / M diretamente.

Atualize para abordar outra resposta

Codificação para a exceção

Usar repositórios não significa ser capaz de mudar a tecnologia de persistência (ou seja, mudar o banco de dados ou usar um serviço da web, etc.). Trata-se de separar a lógica de negócios da persistência para reduzir a complexidade e o acoplamento.

Testes de unidade vs testes de integração

Você não escreve testes de unidade para repositórios. período.

Mas, ao introduzir repositórios (ou qualquer outra camada de abstração entre persistência e negócios), você pode escrever testes de unidade para a lógica de negócios. ou seja, você não precisa se preocupar com a falha de seus testes devido a um banco de dados configurado incorretamente.

Quanto às consultas. Se você usa o LINQ, também precisa ter certeza de que suas consultas funcionam, assim como você tem que fazer com os repositórios. e isso é feito usando testes de integração.

A diferença é que, se você não misturou seu negócio com instruções LINQ, pode ter 100% de certeza de que é o código de persistência que está falhando e não outra coisa.

Se você analisar seus testes, também verá que eles são muito mais limpos se você não tiver interesses mistos (ou seja, LINQ + lógica de negócios)

Exemplos de repositório

A maioria dos exemplos são besteiras. isso é muito verdade. No entanto, se você pesquisar qualquer padrão de design no Google, encontrará muitos exemplos de baixa qualidade. Isso não é motivo para evitar o uso de um padrão.

Construir uma implementação de repositório correta é muito fácil. Na verdade, você só precisa seguir uma única regra:

Não adicione nada na classe do repositório até o momento em que você precisar

Muitos programadores são preguiçosos e tentam fazer um repositório genérico e usar uma classe base com vários métodos de que podem precisar. YAGNI. Você escreve a classe de repositório uma vez e a mantém enquanto o aplicativo durar (pode levar anos). Por que foder tudo por ser preguiçoso. Mantenha-o limpo, sem qualquer herança de classe base. Isso o tornará muito mais fácil de ler e manter.

(A afirmação acima é uma diretriz e não uma lei. Uma classe base pode muito bem ser motivada. Pense antes de adicioná-la, para que você a adicione pelos motivos certos)

Coisas antigas

Conclusão:

Se você não se importa em ter instruções LINQ em seu código de negócios nem se preocupa com testes de unidade, não vejo razão para não usar o Entity Framework diretamente.

Atualizar

Eu fiz um blog sobre o padrão de repositório e o que "abstração" realmente significa: http://blog.gauffin.org/2013/01/repository-pattern-done-right/

Atualização 2

Para o tipo de entidade única com mais de 20 campos, como você projetará o método de consulta para oferecer suporte a qualquer combinação de permutação? Você não quer limitar a pesquisa apenas por nome, que tal pesquisar com propriedades de navegação, listar todos os pedidos com item com código de preço específico, 3 níveis de pesquisa de propriedades de navegação. Todo o motivo IQueryablefoi inventado para poder compor qualquer combinação de pesquisa contra banco de dados. Tudo parece ótimo em teoria, mas a necessidade do usuário vence acima da teoria.

Novamente: uma entidade com mais de 20 campos foi modelada incorretamente. É uma entidade DEUS. Divida-o.

Não estou argumentando que IQueryablenão foi feito para questionar. Estou dizendo que não é certo para uma camada de abstração como o padrão de Repositório, uma vez que vaza. Não existe um provedor LINQ To Sql 100% completo (como EF).

Todos eles têm itens específicos de implementação, como como usar o carregamento antecipado / lento ou como fazer instruções SQL "IN". ExpondoIQueryable no repositório força o usuário a saber todas essas coisas. Portanto, toda a tentativa de abstrair a fonte de dados é um fracasso completo. Você apenas adiciona complexidade sem obter nenhum benefício em usar o OR / M diretamente.

Implemente o padrão de Repositório corretamente ou simplesmente não o use.

(Se você realmente deseja lidar com grandes entidades, pode combinar o padrão Repositório com o padrão Especificação . Isso fornece uma abstração completa que também pode ser testada.)

jgauffin
fonte
6
Não expor IQueryable leva a pesquisas limitadas, e as pessoas acabam criando mais métodos Get para diferentes tipos de consultas e, eventualmente, torna o repositório mais complexo.
Akash Kava
3
você não abordou o problema central em tudo: expor IQueryable por meio de um repositório não é uma abstração completa.
jgauffin
1
Ter um objeto de consulta que contém toda a infraestrutura necessária para ser executado por si só é o caminho a percorrer. Você fornece os campos que são os termos de pesquisa e ele retorna uma lista de resultados. Interno ao QO, você pode fazer o que quiser. E é uma interface facilmente testada. Veja minha postagem acima. E o melhor.
h.alex
2
Pessoalmente, acho que também faz sentido implementar a interface IQueryable <T> em uma classe Repository, em vez de expor o conjunto subjacente em um de seus membros.
dark_perfect de
3
@yat: Um repositório por raiz agregada. Mas imho não é raiz agregada e agregação de tabelas, mas apenas raiz agregada e agregados . O armazenamento real pode usar apenas uma tabela ou muitos deles, ou seja, pode não ser um mapeamento individual entre cada agregado e uma tabela. Eu uso repositórios para reduzir a complexidade e remover quaisquer dependências do armazenamento subjacente.
jgauffin
27

IMO, tanto a Repositoryabstração quanto a UnitOfWorkabstração têm um lugar muito valioso em qualquer desenvolvimento significativo. As pessoas discutirão sobre os detalhes de implementação, mas assim como existem muitas maneiras de esfolar um gato, existem muitas maneiras de implementar uma abstração.

Sua pergunta é especificamente para usar ou não usar e por quê.

Como você sem dúvida percebeu, você já tem esses dois padrões integrados ao Entity Framework, DbContexté o UnitOfWorke DbSeté o Repository. Geralmente, você não precisa fazer o teste de unidade dos próprios UnitOfWorkou Repository, pois eles estão simplesmente facilitando entre suas classes e as implementações de acesso a dados subjacentes. O que você vai precisar fazer, repetidamente, é simular essas duas abstrações ao testar a lógica de seus serviços.

Você pode simular, falsificar ou o que quer que seja com bibliotecas externas adicionando camadas de dependências de código (que você não controla) entre a lógica que está fazendo o teste e a lógica que está sendo testada.

Portanto, um ponto secundário é que ter sua própria abstração UnitOfWorke Repositorylhe dá o máximo de controle e flexibilidade ao simular seus testes de unidade.

Tudo bem, mas para mim, o verdadeiro poder dessas abstrações é que elas fornecem uma maneira simples de aplicar técnicas de Programação Orientada a Aspectos e aderir aos princípios SOLID .

Então você tem o seu IRepository:

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

E sua implementação:

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 fora do comum até agora, mas agora queremos adicionar um pouco de registro - fácil com um decorador de registro .

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();
    }
}

Tudo pronto e sem alterações em nosso código existente . Existem inúmeras outras questões transversais que podemos adicionar, como tratamento de exceções, armazenamento em cache de dados, validação de dados ou qualquer outra coisa e, em todo o nosso processo de design e construção, a coisa mais valiosa que temos que nos permite adicionar recursos simples sem alterar nenhum de nosso código existente é a nossa IRepositoryabstração .

Agora, muitas vezes eu vi esta pergunta no StackOverflow - “como você faz o Entity Framework funcionar em um ambiente multilocatário?”.

https://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+tenant

Se você tem uma Repositoryabstração, a resposta é “é fácil adicionar um 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);
    }
}

IMO, você deve sempre colocar uma abstração simples sobre qualquer componente de terceiros que será referenciado em mais de um punhado de lugares. Sob essa perspectiva, um ORM é o candidato perfeito, pois é referenciado em grande parte do nosso código.

A resposta que normalmente vem à mente quando alguém diz "por que eu deveria ter uma abstração (por exemplo Repository) sobre esta ou aquela biblioteca de terceiros" é "por que você não faria?"

Os decoradores PS são extremamente simples de aplicar usando um recipiente 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>)));
        }
    }
}
qujck
fonte
11

Em primeiro lugar, como sugerido por alguma resposta, o próprio EF é um padrão de repositório, não há necessidade de criar outra abstração apenas para nomeá-lo como repositório.

Repositório de simulação para testes de unidade, realmente precisamos dele?

Deixamos EF se comunicar para testar o banco de dados em testes de unidade para testar nossa lógica de negócios diretamente no banco de dados de teste SQL. Não vejo nenhum benefício em simular qualquer padrão de repositório. O que há de realmente errado em fazer testes de unidade no banco de dados de teste? Como são operações em massa não são possíveis e acabamos escrevendo SQL bruto. SQLite na memória é o candidato perfeito para fazer testes de unidade em banco de dados real.

Abstração desnecessária

Você quer criar um repositório apenas para que no futuro você possa facilmente substituir EF por NHbibernate etc ou qualquer outra coisa? Parece um ótimo plano, mas é realmente econômico?

Linq mata testes de unidade?

Eu gostaria de ver alguns exemplos de como ele pode matar.

Injeção de dependência, IoC

Uau, essas palavras são ótimas, claro que parecem ótimas na teoria, mas às vezes você tem que escolher entre um ótimo design e uma ótima solução. Nós usamos tudo isso e acabamos jogando tudo no lixo e escolhendo uma abordagem diferente. Tamanho x velocidade (tamanho do código e velocidade de desenvolvimento) são muito importantes na vida real. Os usuários precisam de flexibilidade, eles não se importam se seu código é ótimo em design em termos de DI ou IoC.

A menos que você esteja criando o Visual Studio

Todos esses excelentes designs são necessários se você estiver construindo um programa complexo como o Visual Studio ou Eclipse, que será desenvolvido por muitas pessoas e precisa ser altamente personalizável. Todos os grandes padrões de desenvolvimento entraram em cena após anos de desenvolvimento pelos quais esses IDEs passaram, e eles evoluíram em um local onde todos esses grandes padrões de design são tão importantes. Mas se você estiver fazendo uma folha de pagamento simples baseada na web ou um aplicativo de negócios simples, é melhor que você evolua em seu desenvolvimento com o tempo, em vez de gastar tempo construindo-o para milhões de usuários, onde será implantado apenas para centenas de usuários.

Repositório como visualização filtrada - ISecureRepository

Por outro lado, o repositório deve ser uma visão filtrada do EF que protege o acesso aos dados aplicando o preenchimento necessário com base no usuário / função atual.

Mas fazer isso complica o repositório ainda mais, pois ele acaba em uma enorme base de código para manter. As pessoas acabam criando diferentes repositórios para diferentes tipos de usuários ou combinações de tipos de entidades. Além disso, também acabamos com muitos DTOs.

A resposta a seguir é um exemplo de implementação do Repositório Filtrado sem criar um conjunto completo de classes e métodos. Pode não responder a uma pergunta diretamente, mas pode ser útil para derivar uma.

Isenção de responsabilidade: eu sou o autor do SDK REST da entidade

http://entityrestsdk.codeplex.com

Tendo isso em mente, desenvolvemos um SDK que cria um repositório de visualização filtrada com base no SecurityContext que contém filtros para operações CRUD. E apenas dois tipos de regras simplificam quaisquer operações complexas. O primeiro é o acesso à entidade e o outro é a regra de leitura / gravação para a propriedade.

A vantagem é que você não reescreve lógica de negócios ou repositórios para diferentes tipos de usuários, apenas bloqueia ou concede acesso a eles.

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);
  }
}

Essas regras LINQ são avaliadas em relação ao banco de dados no método SaveChanges para cada operação e essas regras atuam como firewall na frente do banco de dados.

Akash Kava
fonte
3
O teste de unidade em um banco de dados significa que você tem requisitos externos extras para seus testes. Se esse banco de dados estiver inativo, ou os dados forem apagados, ou qualquer coisa acontecer a esse banco de dados, seus testes falharão. Isso não é desejado. Repositórios que expõem IQueryable levam cerca de 2 minutos para serem configurados. Não há tempo perdido aqui. Por que o DI demorou tanto? Tudo isso leva minutos. Vou dizer que tudo funcionou muito bem para testar a unidade de minhas consultas complexas em minha camada de serviço. Foi tão bom não precisar de um banco de dados para se conectar. Obter uma estrutura de simulação do nuget levou cerca de um minuto. Isso não leva tempo.
user441521
@ user441521 Repositórios com IQueryable 2 minutos para configurar? em que mundo você está vivendo, cada solicitação do asp.net em nosso site ao vivo é atendida em milissegundos. Zombar e fingir, etc., adiciona mais complexidade ao código, uma perda total de tempo. Os testes de unidade são inúteis quando a unidade não é definida como unidade de lógica de negócios.
Akash Kava de
7

Há muito debate sobre qual método é o correto, então eu vejo como ambos são aceitáveis, então eu uso sempre qual eu gosto mais (que não é repositório, UoW).

No EF UoW é implementado via DbContext e os DbSets são repositórios.

Quanto a como trabalhar com a camada de dados, trabalho diretamente no objeto DbContext, para consultas complexas farei métodos de extensão para a consulta que podem ser reutilizados.

Eu acredito que Ayende também tem alguns posts sobre como abstrair as operações de CUD é ruim.

Eu sempre faço uma interface e tenho meu contexto herdado dela, então posso usar um contêiner IoC para DI.

Josh
fonte
Portanto, os métodos de extensão, quão extensos são? Digamos que eu preciso obter o estado de outra entidade em minha extensão? Essa é a minha maior preocupação agora. Você se importa em mostrar alguns exemplos de métodos de extensão?
Dejan.S
ayende.com/blog/153473/… e ayende.com/blog/153569/… . (Estas são revisões de uma arquitetura (Framework?) Chamada s # arp lite. Geralmente boa, mas ele discorda dos repositórios e abstrações CUD).
Josh
É baseado em NHibernate. Você não tem nenhum exemplo usando EF? E, novamente, quando preciso chamar outra entidade, como isso é feito da melhor forma no método de extensão estática?
Dejan.S
3
Tudo isso é muito bom até que uma propriedade de seu objeto de domínio precise ser hidratada por dados que não estão armazenados em seu banco de dados; ou você precisa aderir a uma tecnologia com mais desempenho do que seu ORM inchado. OOPS! Um ORM simplesmente NÃO é um substituto para um repositório, é um detalhe de implementação de um.
cdaq
2

O que mais se aplica ao EF não é um Padrão de Repositório. É um padrão Facade (abstraindo as chamadas aos métodos EF em versões mais simples e fáceis de usar).

EF é quem aplica o Padrão de Repositório (e também o padrão de Unidade de Trabalho). Ou seja, EF é aquele que abstrai a camada de acesso a dados de forma que o usuário não tenha ideia de que está lidando com SQLServer.

E, por falar nisso, a maioria dos "repositórios" sobre o EF nem mesmo são bons Facades, pois eles meramente mapeiam, de forma bastante direta, para métodos únicos no EF, até o ponto de ter as mesmas assinaturas.

As duas razões, então, para aplicar este padrão "Repositório" sobre o EF é permitir testes mais fáceis e estabelecer um subconjunto de chamadas "enlatadas" para ele. Não é ruim em si, mas claramente não é um Repositório.

Aratirn
fonte
1

Linq é hoje um 'Repositório'.

ISession + Linq já é o repositório, e você não precisa de GetXByYmétodos nemQueryData(Query q) generalização. Sendo um pouco paranóico com o uso de DAL, ainda prefiro a interface de repositório. (Do ponto de vista da sustentabilidade, ainda precisamos ter alguma fachada sobre interfaces de acesso a dados específicas).

Aqui está o repositório que usamos - ele nos desvincula do uso direto do nhibernate, mas fornece interface linq (como acesso ISession em casos excepcionais, que estão sujeitos a refatoração eventualmente).

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}
Mikalai
fonte
O que você faz para a camada de serviço?
Dejan.S
Os controladores consultam o repo para dados somente leitura, por que adicionar uma camada extra? A outra possibilidade é usar "ContentService", que cada vez mais tende a ser um repositório de nível de serviço: GetXByY, etc.etc. Para operações de modificação - serviços de aplicativo são apenas abstrações sobre casos de uso - eles usam BL e repo livremente ..
mikalai
Estou acostumado a fazer camada de serviço para lógica de negócios. Não tenho certeza se sigo o que você com ContentService, explique. Seria uma má prática fazer classes auxiliares como "camada de serviço"?
Dejan.S
Por 'camada de serviço', quero dizer 'serviços de aplicativo'. Eles podem usar o repositório e qualquer outra parte pública da camada de domínio. "Camada de serviço" não é uma prática ruim, mas eu evitaria fazer a classe XService apenas para fornecer o resultado List <X>. O campo de comentário parece ser muito curto para descrever os serviços em detalhes, desculpe.
mikalai
E se, digamos, um cálculo de carrinho e você precise obter parâmetros de configurações do aplicativo e parâmetros específicos do cliente para fazer um cálculo, e isso é reutilizado em vários locais no aplicativo. Como você lida com essa situação? classe auxiliar ou serviço de aplicativo?
Dejan.S
1

O Repositório (ou como alguém quiser chamá-lo) neste momento, para mim, trata principalmente de abstrair a camada de persistência.

Eu o uso junto com objetos de consulta, portanto, não tenho um acoplamento a nenhuma tecnologia específica em meus aplicativos. E também facilita muito os testes.

Então, eu tendo a ter

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);
}

Possivelmente adicione métodos assíncronos com retornos de chamada como delegados. O repo é fácil de implementar genericamente , portanto, não posso tocar em uma linha da implementação de aplicativo para aplicativo. Bem, isso é verdade pelo menos quando uso NH, eu fiz isso também com EF, mas me fez odiar EF. 4. A conversa é o início de uma transação. Muito legal se algumas classes compartilham a instância do repositório. Além disso, para NH, um repo em minha implementação é igual a uma sessão que é aberta na primeira solicitação.

Em seguida, os 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 o configure utilizo no NH apenas para passar no ISession. Em EF não faz sentido mais ou menos.

Um exemplo de consulta seria .. (NH)

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

Para fazer uma consulta EF, você teria que ter o contexto na base abstrata, não na sessão. Mas é claro que o ifc seria o mesmo.

Desta forma, as próprias consultas são encapsuladas e facilmente testáveis. E o melhor de tudo, meu código depende apenas de interfaces. Tudo muito limpo. Objetos de domínio (negócios) são apenas isso, por exemplo, não há mistura de responsabilidades como ao usar o padrão de registro ativo que é dificilmente testável e mistura código de acesso a dados (consulta) no objeto de domínio e, ao fazer isso, está misturando preocupações (objeto que busca em si ??). Todos ainda estão livres para criar POCOs para transferência de dados.

Em suma, muita reutilização e simplicidade de código são fornecidas com essa abordagem, sem que eu possa imaginar nada. Alguma ideia?

E muito obrigado ao Ayende por suas ótimas postagens e contínua dedicação. São as ideias dele aqui (objeto de consulta), não minhas.

h.alex
fonte
1
As entidades de persistência (seus POCOs) NÃO são entidades de negócios / domínio. E o objetivo do repositório é desacoplar a camada de negócios (ou qualquer) da persistência.
MikeSW
Não consigo ver o acoplamento. Concordo um pouco com a parte POCO, mas não me importo. Nada o impede de ter POCOs 'genuínos' e ainda usar essa abordagem.
h.alex
1
As entidades não precisam ser POCOs idiotas. Na verdade, modelar a lógica de negócios em entidades é o que a turma do DDD faz o tempo todo. Este estilo de desenvolvimento combina muito bem com NH ou EF.
chris
1

Para mim, é uma decisão simples, com relativamente poucos fatores. Os fatores são:

  1. Repositórios são para classes de domínio.
  2. Em alguns dos meus aplicativos, as classes de domínio são iguais às minhas classes de persistência (DAL), em outros não.
  3. Quando eles são iguais, a EF já está me fornecendo Repositórios.
  4. EF fornece carregamento lento e IQueryable. Eu gosto destes.
  5. Abstraindo / 'facatando' / reimplementando o repositório sobre o EF geralmente significa perda de preguiça e IQueryable

Portanto, se meu aplicativo não pode justificar o nº 2, domínio separado e modelos de dados, geralmente não vou me preocupar com o nº 5

Chalky
fonte