Qual é a melhor prática no EF Core para usar chamadas assíncronas paralelas com um DbContext injetado?
Eu tenho uma API do .NET Core 1.1 com EF Core 1.1 e usando a instalação básica da Microsoft usando o Injection de dependência para fornecer o DbContext aos meus serviços. (Referência:https://docs.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro#register-the-context-with-dependency-injection)
Agora, estou olhando para leituras paralelas de banco de dados como uma otimização usandoWhenAll
Então, em vez de:
var result1 = await _dbContext.TableModel1.FirstOrDefaultAsync(x => x.SomeId == AnId);
var result2 = await _dbContext.TableModel2.FirstOrDefaultAsync(x => x.SomeOtherProp == AProp);
Eu uso:
var repositoryTask1 = _dbContext.TableModel1.FirstOrDefaultAsync(x => x.SomeId == AnId);
var repositoryTask2 = _dbContext.TableModel2.FirstOrDefaultAsync(x => x.SomeOtherProp == AProp);
(var result1, var result2) = await (repositoryTask1, repositoryTask2 ).WhenAll();
Tudo está bem, até eu usar a mesma estratégia fora dessas classes de acesso ao repositório de banco de dados e chamar esses mesmos métodos com WhenAll no meu controlador em vários serviços:
var serviceTask1 = _service1.GetSomethingsFromDb(Id);
var serviceTask2 = _service2.GetSomeMoreThingsFromDb(Id);
(var dataForController1, var dataForController2) = await (serviceTask1, serviceTask2).WhenAll();
Agora, quando eu chamo isso do meu controlador, recebo aleatoriamente erros de concorrência como:
System.InvalidOperationException: ExecuteReader requer uma conexão aberta e disponível. O estado atual da conexão está fechado.
A razão pela qual acredito é que, às vezes, esses threads tentam acessar as mesmas tabelas ao mesmo tempo.Eu sei que isso ocorre por design no EF Core e se eu quisesse, poderia criar um novo dbContext toda vez, mas estou tentando ver se há uma solução alternativa. Foi quando eu encontrei este bom post de Mehdi El Gueddari:http://mehdi.me/ambient-dbcontext-in-ef6/
Em que ele reconhece esta limitação:
um DbContext injetado impede que você possa introduzir multi-threading ou qualquer tipo de fluxo de execução paralelo em seus serviços.
E oferece uma solução alternativa personalizada comDbContextScope
.
No entanto, ele apresenta uma ressalva mesmo com o DbContextScope, pois ele não funcionará em paralelo (o que estou tentando fazer acima):
se você tentar iniciar várias tarefas paralelas no contexto de um DbContextScope (por exemplo, criando vários threads ou várias tarefas TPL), você terá grandes problemas. Isso ocorre porque o ambiente DbContextScope fluirá por todos os threads que suas tarefas paralelas estão usando.
Seu ponto final aqui me leva à minha pergunta:
Em geral, o acesso paralelo ao banco de dados em uma única transação comercial tem pouco ou nenhum benefício e apenas adiciona complexidade significativa. Qualquer operação paralela executada no contexto de uma transação comercial não deve acessar o banco de dados.
Não devo usar o WhenAll nesse caso nos meus controladores e continuar usando o aguardar um por um? Ou a injeção de dependência do DbContext é o problema mais fundamental aqui; portanto, um novo problema deve ser criado / fornecido a cada momento por algum tipo de fábrica?