Eu tenho lido muitos artigos explicando como configurar o Entity Framework DbContext
para que apenas um seja criado e usado por solicitação da Web HTTP usando várias estruturas de DI.
Por que essa é uma boa idéia em primeiro lugar? Que vantagens você ganha ao usar essa abordagem? Existem certas situações em que isso seria uma boa idéia? Existem coisas que você pode fazer usando essa técnica que não pode instanciar ao instanciar DbContext
s por chamada de método de repositório?
Respostas:
Vamos começar ecoando para Ian: ter um single
DbContext
para todo o aplicativo é uma má ideia. A única situação em que isso faz sentido é quando você tem um aplicativo de thread único e um banco de dados que é usado exclusivamente por essa instância de aplicativo único. ODbContext
não é seguro para threads e, como osDbContext
dados são armazenados em cache, ficam obsoletos em breve. Isso causará todo tipo de problema quando vários usuários / aplicativos trabalharem nesse banco de dados simultaneamente (o que é muito comum, é claro). Mas espero que você já saiba disso e só queira saber por que não injetar uma nova instância (ou seja, com um estilo de vida transitório) daDbContext
pessoa que precisar. (para obter mais informações sobre por que um únicoDbContext
- ou mesmo no contexto por segmento - é ruim, leia esta resposta ).Deixe-me começar dizendo que registrar um
DbContext
como transitório pode funcionar, mas geralmente você deseja ter uma única instância dessa unidade de trabalho dentro de um determinado escopo. Em um aplicativo da web, pode ser prático definir esse escopo nos limites de uma solicitação da web; portanto, um estilo de vida Por solicitação da Web. Isso permite que você permita que todo um conjunto de objetos opere no mesmo contexto. Em outras palavras, eles operam dentro da mesma transação comercial.Se você não tem o objetivo de ter um conjunto de operações operando dentro do mesmo contexto, nesse caso, o estilo de vida transitório é bom, mas há algumas coisas a serem observadas:
_context.SaveChanges()
(caso contrário, as alterações seriam perdidas). Isso pode complicar o seu código e adicionar uma segunda responsabilidade ao código (a responsabilidade de controlar o contexto) e é uma violação do Princípio da Responsabilidade Única .DbContext
] nunca deixem o escopo de uma classe, porque elas não podem ser usadas na instância de contexto de outra classe. Isso pode complicar bastante o seu código, porque quando você precisa dessas entidades, precisa carregá-las novamente por id, o que também pode causar problemas de desempenho.DbContext
implementaIDisposable
, você provavelmente ainda deseja Dispor todas as instâncias criadas. Se você quiser fazer isso, basicamente tem duas opções. Você precisa descartá-los no mesmo método logo após a chamadacontext.SaveChanges()
, mas, nesse caso, a lógica comercial assume a propriedade de um objeto que é passado externamente. A segunda opção é Dispor todas as instâncias criadas no limite da solicitação de HTTP, mas nesse caso você ainda precisa de algum tipo de escopo para informar ao contêiner quando essas instâncias precisam ser descartadas.Outra opção é não injetar um
DbContext
. Em vez disso, você injeta umDbContextFactory
que é capaz de criar uma nova instância (eu costumava usar essa abordagem no passado). Dessa forma, a lógica de negócios controla o contexto explicitamente. Se for assim:O lado positivo disso é que você gerencia a vida do
DbContext
explicitamente e é fácil configurá-lo. Ele também permite que você use um contexto único em um determinado escopo, o que possui vantagens claras, como executar código em uma única transação comercial e ser capaz de repassar entidades, pois elas se originam da mesmaDbContext
.A desvantagem é que você terá que passar pelo
DbContext
método from to method (que é chamado de Injeção de método). Observe que, em certo sentido, essa solução é igual à abordagem 'escopo', mas agora o escopo é controlado no próprio código do aplicativo (e possivelmente é repetido várias vezes). É o aplicativo responsável pela criação e descarte da unidade de trabalho. Como oDbContext
é criado após a construção do gráfico de dependência, a Injeção de construtor fica fora de cena e você precisa adiar para a Injeção de método quando precisar passar o contexto de uma classe para outra.A injeção de método não é tão ruim, mas quando a lógica de negócios se torna mais complexa e mais classes são envolvidas, você terá que passar de método para método e de classe para classe, o que pode complicar bastante o código (eu já vi isso no passado). Para uma aplicação simples, essa abordagem funciona bem.
Devido às desvantagens, essa abordagem de fábrica tem para sistemas maiores, outra abordagem pode ser útil e é a que permite que o contêiner ou o código de infraestrutura / Raiz da Composição gerenciem a unidade de trabalho. Esse é o estilo de sua pergunta.
Ao permitir que o contêiner e / ou a infraestrutura lidem com isso, o código do aplicativo não é poluído, pois é necessário criar (opcionalmente) confirmar e Dispose uma instância UoW, o que mantém a lógica de negócios simples e limpa (apenas uma responsabilidade única). Existem algumas dificuldades com essa abordagem. Por exemplo, você confirmou e descartou a instância?
A eliminação de uma unidade de trabalho pode ser feita no final da solicitação da web. Muitas pessoas, no entanto, assumem incorretamente que este também é o local para comprometer a unidade de trabalho. No entanto, nesse ponto do aplicativo, você simplesmente não pode determinar com certeza que a unidade de trabalho deve realmente ser confirmada. Por exemplo, se o código da camada de negócios gerou uma exceção que foi capturada mais acima na pilha de chamadas, você definitivamente não deseja Confirmar.
A solução real é novamente gerenciar explicitamente algum tipo de escopo, mas desta vez faça-o dentro da Raiz da Composição. Abstraindo toda a lógica de negócios por trás do padrão de comando / manipulador , você poderá escrever um decorador que possa ser agrupado em torno de cada manipulador de comando que permita fazer isso. Exemplo:
Isso garante que você só precise escrever esse código de infraestrutura uma vez. Qualquer contêiner DI sólido permite que você configure esse decorador para envolver todas as
ICommandHandler<T>
implementações de maneira consistente.fonte
CreateCommand<TEnity>
e um genéricoCreateCommandHandler<TEntity> : ICommandHandler<CreateCommand<TEntity>>
(e fazer o mesmo para Atualizar e Excluir e teve uma únicaGetByIdQuery<TEntity>
consulta). Ainda assim, você deve se perguntar se esse modelo é uma abstração útil para operações CRUD ou se apenas adiciona complexidade. Ainda assim, você pode se beneficiar da possibilidade de adicionar facilmente preocupações transversais (por meio de decoradores) usando esse modelo. Você terá que pesar os prós e contras.TransactionCommandHandlerDecorator
? por exemplo, se a classe decorada forInsertCommandHandler
class, como ela poderá registrar a operação de inserção no contexto (DbContext no EF)?Existem duas recomendações contraditórias da microsoft e muitas pessoas usam o DbContexts de uma maneira completamente divergente.
Isso se contradiz porque, se a sua solicitação não estiver relacionada com o material do banco de dados, o seu DbContext será mantido sem motivo. Portanto, é um desperdício manter seu DbContext ativo enquanto sua solicitação está apenas aguardando a conclusão de coisas aleatórias ...
Muitas pessoas que seguem a regra 1 têm seus DbContexts dentro de seu "Padrão de Repositório" e criam uma nova Instância por Consulta ao Banco de Dados, de modo que X * DbContext por Solicitação
Eles apenas obtêm seus dados e descartam o contexto o mais rápido possível. Isso é considerado por MUITAS pessoas uma prática aceitável. Embora isso tenha os benefícios de ocupar seus recursos de banco de dados pelo tempo mínimo, ele sacrifica claramente todo o doce UnitOfWork e Caching Candy que a EF tem a oferecer.
Manter viva uma única instância multiuso do DbContext maximiza os benefícios do Caching, mas como o DbContext não é seguro para threads e cada solicitação da Web é executada em seu próprio thread, um DbContext por solicitação é o maior tempo possível.
Portanto, a recomendação da equipe da EF sobre o uso de 1 Db Context por solicitação é claramente baseada no fato de que em um aplicativo Web o UnitOfWork provavelmente estará dentro de uma solicitação e essa solicitação possui um encadeamento. Portanto, um DbContext por solicitação é como o benefício ideal do UnitOfWork e Caching.
Mas em muitos casos isso não é verdade. Considero que o log de um UnitOfWork separado, portanto, ter um novo DbContext para log de pós-solicitação em threads assíncronos é completamente aceitável
Então, finalmente, verifica-se que a vida útil de um DbContext está restrita a esses dois parâmetros. UnitOfWork e Thread
fonte
Nenhuma resposta aqui realmente responde à pergunta. O OP não perguntou sobre um design DbContext de singleton / por aplicativo, ele perguntou sobre um design de solicitação por Web () e quais benefícios potenciais poderiam existir.
Vou fazer referência a http://mehdi.me/ambient-dbcontext-in-ef6/, pois o Mehdi é um recurso fantástico:
Lembre-se de que há contras também. Esse link contém muitos outros recursos para ler sobre o assunto.
Basta postar isso no caso de alguém tropeçar nessa pergunta e não ser absorvido pelas respostas que não abordam a questão.
fonte
Tenho certeza de que é porque o DbContext não é totalmente seguro para threads. Portanto, compartilhar a coisa nunca é uma boa ideia.
fonte
Uma coisa que não é realmente abordada na pergunta ou na discussão é o fato de o DbContext não poder cancelar as alterações. Você pode enviar alterações, mas não pode limpar a árvore de alterações. Portanto, se você usar um contexto por solicitação, não terá sorte se precisar jogar fora as alterações por qualquer motivo.
Pessoalmente, crio instâncias do DbContext quando necessário - geralmente anexadas a componentes de negócios que têm a capacidade de recriar o contexto, se necessário. Dessa forma, eu tenho controle sobre o processo, em vez de ter uma única instância forçada para mim. Também não preciso criar o DbContext em cada inicialização do controlador, independentemente de ele realmente ser usado. Então, se ainda quero ter instâncias por solicitação, posso criá-las no CTOR (via DI ou manualmente) ou criá-las conforme necessário em cada método do controlador. Pessoalmente, costumo usar a última abordagem para evitar a criação de instâncias DbContext quando elas não são realmente necessárias.
Depende de qual ângulo você olha para ele também. Para mim, a instância por solicitação nunca fez sentido. O DbContext realmente pertence à solicitação de HTTP? Em termos de comportamento, esse é o lugar errado. Seus componentes de negócios devem criar seu contexto, não a solicitação de HTTP. Em seguida, você pode criar ou jogar fora seus componentes de negócios conforme necessário e nunca se preocupar com a vida útil do contexto.
fonte
Eu concordo com opiniões anteriores. É bom dizer que, se você deseja compartilhar o DbContext no aplicativo de thread único, precisará de mais memória. Por exemplo, meu aplicativo Web no Azure (uma instância extra pequena) precisa de mais 150 MB de memória e eu tenho cerca de 30 usuários por hora.
Aqui está um exemplo real da imagem: o aplicativo foi implantado às 12h
fonte
O que eu mais gosto é que alinha a unidade de trabalho (como o usuário a vê - ou seja, um envio de página) com a unidade de trabalho no sentido ORM.
Portanto, você pode tornar o envio da página inteiro transacional, o que não seria possível se estivesse expondo os métodos CRUD, cada um criando um novo contexto.
fonte
Outro motivo discreto para não usar um DbContext singleton, mesmo em um único aplicativo de usuário único encadeado, é devido ao padrão de mapa de identidade que ele usa. Isso significa que toda vez que você recuperar dados usando consulta ou por ID, ele manterá as instâncias da entidade recuperada em cache. Na próxima vez que você recuperar a mesma entidade, ela fornecerá a instância em cache da entidade, se disponível, com as modificações que você fez na mesma sessão. Isso é necessário para que o método SaveChanges não termine com várias instâncias de entidade diferentes dos mesmos registros do banco de dados; caso contrário, o contexto teria que, de alguma forma, mesclar os dados de todas essas instâncias de entidade.
O motivo é que um DbContext único pode se tornar uma bomba-relógio que pode eventualmente armazenar em cache todo o banco de dados + a sobrecarga de objetos .NET na memória.
Existem maneiras de contornar esse comportamento usando apenas consultas Linq com o
.NoTracking()
método de extensão. Hoje em dia, os PCs têm muita RAM. Mas geralmente esse não é o comportamento desejado.fonte
Outro problema a ser observado especificamente no Entity Framework é o uso de uma combinação de criação de novas entidades, carregamento lento e, em seguida, o uso dessas novas entidades (do mesmo contexto). Se você não usar IDbSet.Create (apenas um novo), o carregamento lento nessa entidade não funcionará quando for recuperado do contexto em que foi criado. Exemplo:
fonte