Estou trabalhando em um projeto ASP.Net Core 2.0 usando Entity Framework Core
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0"/>
E em um dos métodos de minha lista, estou recebendo este erro:
InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
Este é o meu método:
[HttpGet("{currentPage}/{pageSize}/")]
[HttpGet("{currentPage}/{pageSize}/{search}")]
public ListResponseVM<ClientVM> GetClients([FromRoute] int currentPage, int pageSize, string search)
{
var resp = new ListResponseVM<ClientVM>();
var items = _context.Clients
.Include(i => i.Contacts)
.Include(i => i.Addresses)
.Include("ClientObjectives.Objective")
.Include(i => i.Urls)
.Include(i => i.Users)
.Where(p => string.IsNullOrEmpty(search) || p.CompanyName.Contains(search))
.OrderBy(p => p.CompanyName)
.ToPagedList(pageSize, currentPage);
resp.NumberOfPages = items.TotalPage;
foreach (var item in items)
{
var client = _mapper.Map<ClientVM>(item);
client.Addresses = new List<AddressVM>();
foreach (var addr in item.Addresses)
{
var address = _mapper.Map<AddressVM>(addr);
address.CountryCode = addr.CountryId;
client.Addresses.Add(address);
}
client.Contacts = item.Contacts.Select(p => _mapper.Map<ContactVM>(p)).ToList();
client.Urls = item.Urls.Select(p => _mapper.Map<ClientUrlVM>(p)).ToList();
client.Objectives = item.Objectives.Select(p => _mapper.Map<ObjectiveVM>(p)).ToList();
resp.Items.Add(client);
}
return resp;
}
Estou um pouco perdido, especialmente porque funciona quando o executo localmente, mas quando implanto no meu servidor de teste (IIS 8.5), recebo esse erro e estava funcionando normalmente. O erro começou a aparecer depois de aumentar o comprimento máximo de um dos meus modelos. Eu também atualizei o comprimento máximo do modelo de vista correspondente. E existem muitos outros métodos de lista que são muito semelhantes e estão funcionando.
Eu tinha um trabalho Hangfire em execução, mas esse trabalho não usa a mesma entidade. Isso é tudo que consigo pensar para ser relevante. Alguma ideia do que pode estar causando isso?
fonte
Respostas:
Não tenho certeza se você está usando IoC e injeção de dependência para resolver seu DbContext onde quer que ele possa ser usado. Se você fizer isso e estiver usando IoC nativo do .NET Core (ou qualquer outro IoC-Container) e estiver recebendo este erro, certifique-se de registrar seu DbContext como Transiente. Faz
OU
ao invés de
AddDbContext adiciona o contexto como escopo, o que pode causar problemas ao trabalhar com vários threads.
Além disso, operações assíncronas / aguardar podem causar esse comportamento, ao usar expressões lambda assíncronas.
Adicioná-lo como temporário também tem suas desvantagens. Você não poderá fazer alterações em alguma entidade em várias classes que estão usando o contexto porque cada classe obterá sua própria instância de seu DbContext.
A explicação simples para isso é que a
DbContext
implementação não é segura para threads. Você pode ler mais sobre isso aquifonte
Task.Run(async () => context.Set...)
sem esperar ou criando um contexto db com escopo sem esperar o resultado. Isso significa que seu contexto provavelmente já está descartado ao acessá-lo. Se você estiver no Microsoft DI, deve criar um escopo de dependência dentro deleTask.Run
. Verifique esses links também. stackoverflow.com/questions/45047877/… docs.microsoft.com/en-us/dotnet/api/…Em alguns casos, esse erro ocorre ao chamar um método assíncrono sem a
await
palavra - chave, o que pode ser resolvido simplesmente adicionandoawait
antes da chamada do método. no entanto, a resposta pode não estar relacionada à pergunta mencionada, mas pode ajudar a resolver um erro semelhante.fonte
First()
paraawait / FirstAsync()
funcionou.A exceção significa que
_context
está sendo usado por dois threads ao mesmo tempo; dois threads na mesma solicitação ou por duas solicitações.A sua
_context
declaração estática, talvez? Não deveria ser.Ou você está chamando
GetClients
várias vezes na mesma solicitação de algum outro lugar no seu código?Você pode já estar fazendo isso, mas idealmente, você usaria injeção de dependência para o seu
DbContext
, o que significa que você estará usandoAddDbContext()
em seu Startup.cs, e seu construtor de controlador será parecido com isto:private readonly MyDbContext _context; //not static public MyController(MyDbContext context) { _context = context; }
Se o seu código não é assim, mostre-nos e talvez possamos ajudar mais.
fonte
_context
objeto em outras threads? Como dentro de umTask.Run()
por exemplo?await
métodos assíncronos. Se você não usarawait
, você pode acidentalmente entrar em multi-threading.Resolva meu problema usando esta linha de código em meu arquivo Startup.cs.
Adicionar um serviço temporário significa que cada vez que o serviço é solicitado, uma nova instância é criada quando você está trabalhando com injeção de dependência
services.AddDbContext<Context>(options => options.UseSqlServer(_configuration.GetConnectionString("ContextConn")), ServiceLifetime.Transient);
fonte
Eu tive o mesmo problema e descobri que o serviço aos pais era um singelton. Portanto, o contexto tornou-se automaticamente único também. Embora tenha sido declarado como Per Life Time Scoped in DI.
Injetando serviço com diferentes vidas em outro
Nunca injete serviços com escopo e temporário no serviço Singleton. (Isso converte efetivamente o serviço transitório ou com escopo no singleton.)
Nunca injete serviços temporários em serviço com escopo (isso converte o serviço transiente em serviço com escopo definido).
fonte
Eu tive o mesmo erro. Aconteceu porque chamei um método que foi construído como em
public async void ...
vez depublic async Task ...
.fonte
Acho que essa resposta ainda pode ajudar alguém e economizar muitas vezes. Resolvi um problema semelhante mudando
IQueryable
paraList
(ou para array, coleção ...).Por exemplo:
var list=_context.table1.where(...);
para
var list=_context.table1.where(...).ToList(); //or ToArray()...
fonte
Enfrentei o mesmo problema, mas o motivo não foi nenhum dos listados acima. Eu criei uma tarefa, criei um escopo dentro da tarefa e pedi ao container para obter um serviço. Funcionou bem, mas depois usei um segundo serviço dentro da tarefa e esqueci de solicitá-lo também para o novo escopo. Por conta disso, o 2º serviço estava utilizando um DbContext que já estava descartado.
Task task = Task.Run(() => { using (var scope = serviceScopeFactory.CreateScope()) { var otherOfferService = scope.ServiceProvider.GetService<IOfferService>(); // everything was ok here. then I did: productService.DoSomething(); // (from the main scope) and this failed because the db context associated to that service was already disposed. ... } }
Eu deveria ter feito isso:
var otherProductService = scope.ServiceProvider.GetService<IProductService>(); otherProductService.DoSomething();
fonte
Entity Framework Core não oferece suporte a várias operações paralelas sendo executadas no mesmo
DbContext
instância. Isso inclui a execução paralela deasync
consultas e qualquer uso simultâneo explícito de vários threads. Portanto, sempreawait async
chame imediatamente ou useDbContext
instâncias separadas para operações executadas em paralelo.fonte
Minha situação é diferente: eu estava tentando propagar o banco de dados com 30 usuários, pertencentes a funções específicas, então estava executando este código:
for (var i = 1; i <= 30; i++) { CreateUserWithRole("Analyst", $"analyst{i}", UserManager); }
Esta era uma função de sincronização. Dentro dele, eu tinha 3 ligações para:
Quando substituí
.Result
por.GetAwaiter().GetResult()
, esse erro foi embora.fonte
Eu recebi a mesma mensagem. Mas não faz sentido no meu caso. Meu problema é que usei uma propriedade "NotMapped" por engano. Provavelmente significa apenas um erro de sintaxe Linq ou classe de modelo em alguns casos. A mensagem de erro parece enganosa. O significado original desta mensagem é que você não pode chamar async no mesmo dbcontext mais de uma vez na mesma solicitação.
[NotMapped] public int PostId { get; set; } public virtual Post Post { get; set; }
Você pode verificar este link para obter detalhes, https://www.softwareblogs.com/Posts/Details/5/error-a-second-operation-started-on-this-context-before-a-previous-operation-completed
fonte
Tenho um serviço em segundo plano que executa uma ação para cada entrada em uma tabela. O problema é que, se eu iterar e modificar alguns dados, todos na mesma instância do DbContext, esse erro ocorre.
Uma solução, conforme mencionado neste tópico, é alterar o tempo de vida do DbContext para temporário, definindo-o como
mas porque eu faço alterações em vários serviços diferentes e as comprometo de uma vez usando o
SaveChanges()
método, essa solução não funciona no meu caso.Como meu código é executado em um serviço, eu estava fazendo algo como
using (var scope = Services.CreateScope()) { var entities = scope.ServiceProvider.GetRequiredService<IReadService>().GetEntities(); var writeService = scope.ServiceProvider.GetRequiredService<IWriteService>(); foreach (Entity entity in entities) { writeService.DoSomething(entity); } }
poder usar o serviço como se fosse um simples pedido. Então, para resolver o problema, apenas divido o escopo único em dois, um para a consulta e outro para as operações de gravação, assim:
using (var readScope = Services.CreateScope()) using (var writeScope = Services.CreateScope()) { var entities = readScope.ServiceProvider.GetRequiredService<IReadService>().GetEntities(); var writeService = writeScope.ServiceProvider.GetRequiredService<IWriteService>(); foreach (Entity entity in entities) { writeService.DoSomething(entity); } }
Assim, existem efetivamente duas instâncias diferentes do DbContext sendo usado.
Outra solução possível seria certificar-se de que a operação de leitura foi encerrada antes de iniciar a iteração. Isso não é muito prático no meu caso, porque poderia haver muitos resultados que precisariam ser carregados na memória para a operação que tentei evitar usando um Queryable em primeiro lugar.
fonte
Consegui obter esse erro passando um
IQueryable
para um método que usava essa 'lista' IQueryable como parte de uma outra consulta para o mesmo contexto.public void FirstMethod() { // This is returning an IQueryable var stockItems = _dbContext.StockItems .Where(st => st.IsSomething); SecondMethod(stockItems); } public void SecondMethod(IEnumerable<Stock> stockItems) { var grnTrans = _dbContext.InvoiceLines .Where(il => stockItems.Contains(il.StockItem)) .ToList(); }
Para impedir que isso aconteça, usei a abordagem aqui e materializei essa lista antes de passar o segundo método, alterando a chamada
SecondMethod
para serSecondMethod(stockItems.ToList()
fonte
Primeiro, vote positivamente (pelo menos) na resposta de alsami. Isso me colocou no caminho certo.
Mas para aqueles que estão fazendo IoC, aqui está um mergulho um pouco mais profundo.
Meu erro (igual aos outros)
Minha configuração de código. "Apenas o básico" ...
public class MyCoolDbContext: DbContext{ public DbSet <MySpecialObject> MySpecialObjects { get; set; } }
e
public interface IMySpecialObjectDomainData{}
e (observe que MyCoolDbContext está sendo injetado)
public class MySpecialObjectEntityFrameworkDomainDataLayer: IMySpecialObjectDomainData{ public MySpecialObjectEntityFrameworkDomainDataLayer(MyCoolDbContext context) { /* HERE IS WHERE TO SET THE BREAK POINT, HOW MANY TIMES IS THIS RUNNING??? */ this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null); } }
e
public interface IMySpecialObjectManager{}
e
public class MySpecialObjectManager: IMySpecialObjectManager { public const string ErrorMessageIMySpecialObjectDomainDataIsNull = "IMySpecialObjectDomainData is null"; private readonly IMySpecialObjectDomainData mySpecialObjectDomainData; public MySpecialObjectManager(IMySpecialObjectDomainData mySpecialObjectDomainData) { this.mySpecialObjectDomainData = mySpecialObjectDomainData ?? throw new ArgumentNullException(ErrorMessageIMySpecialObjectDomainDataIsNull, (Exception)null); } }
E, finalmente, minha classe multithread, sendo chamada de um aplicativo de console (aplicativo de interface de linha de comando)
public interface IMySpecialObjectThatSpawnsThreads{}
e
public class MySpecialObjectThatSpawnsThreads: IMySpecialObjectThatSpawnsThreads { public const string ErrorMessageIMySpecialObjectManagerIsNull = "IMySpecialObjectManager is null"; private readonly IMySpecialObjectManager mySpecialObjectManager; public MySpecialObjectThatSpawnsThreads(IMySpecialObjectManager mySpecialObjectManager) { this.mySpecialObjectManager = mySpecialObjectManager ?? throw new ArgumentNullException(ErrorMessageIMySpecialObjectManagerIsNull, (Exception)null); } }
e o acúmulo de DI. (Novamente, isso é para um aplicativo de console (interface de linha de comando) ... que exibe um comportamento ligeiramente diferente dos aplicativos da web)
private static IServiceProvider BuildDi(IConfiguration configuration) { /* this is being called early inside my command line application ("console application") */ string defaultConnectionStringValue = string.Empty; /* get this value from configuration */ ////setup our DI IServiceCollection servColl = new ServiceCollection() ////.AddLogging(loggingBuilder => loggingBuilder.AddConsole()) /* THE BELOW TWO ARE THE ONES THAT TRIPPED ME UP. */ .AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>() .AddTransient<IMySpecialObjectManager, MySpecialObjectManager>() /* so the "ServiceLifetime.Transient" below................is what you will find most commonly on the internet search results */ # if (MY_ORACLE) .AddDbContext<ProvisioningDbContext>(options => options.UseOracle(defaultConnectionStringValue), ServiceLifetime.Transient); # endif # if (MY_SQL_SERVER) .AddDbContext<ProvisioningDbContext>(options => options.UseSqlServer(defaultConnectionStringValue), ServiceLifetime.Transient); # endif servColl.AddSingleton <IMySpecialObjectThatSpawnsThreads, MySpecialObjectThatSpawnsThreads>(); ServiceProvider servProv = servColl.BuildServiceProvider(); return servProv; }
Os que me surpreenderam foram os (mudar para) transitórios para
Observe, eu acho que como IMySpecialObjectManager estava sendo injetado em "MySpecialObjectThatSpawnsThreads", esses objetos injetados precisavam ser Transientes para completar a cadeia.
O ponto é ....... não era apenas o (My) DbContext que precisava .Transient ... mas uma parte maior do gráfico DI.
Dica de depuração:
Está linha:
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
Coloque seu ponto de interrupção do depurador lá. Se seu MySpecialObjectThatSpawnsThreads está gerando um número N de threads (digamos 10 threads, por exemplo) ...... e essa linha está sendo atingida apenas uma vez ... esse é o seu problema. Seu DbContext está cruzando tópicos.
BÔNUS:
Eu sugeriria a leitura deste url / artigo abaixo (antigo, mas bom) sobre as diferenças entre aplicativos da web e aplicativos de console
https://mehdi.me/ambient-dbcontext-in-ef6/
Aqui está o cabeçalho do artigo, caso o link mude.
Eu encontrei este problema com WorkFlowCore https://github.com/danielgerlag/workflow-core
<ItemGroup> <PackageReference Include="WorkflowCore" Version="3.1.5" /> </ItemGroup>
exemplo de código abaixo .. para ajudar futuros pesquisadores da Internet
namespace MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Workflows { using System; using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Constants; using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Glue; using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.WorkflowSteps; using WorkflowCore.Interface; using WorkflowCore.Models; public class MySpecialObjectInterviewDefaultWorkflow : IWorkflow<MySpecialObjectInterviewPassThroughData> { public const string WorkFlowId = "MySpecialObjectInterviewWorkflowId"; public const int WorkFlowVersion = 1; public string Id => WorkFlowId; public int Version => WorkFlowVersion; public void Build(IWorkflowBuilder<MySpecialObjectInterviewPassThroughData> builder) { builder .StartWith(context => { Console.WriteLine("Starting workflow..."); return ExecutionResult.Next(); }) /* bunch of other Steps here that were using IMySpecialObjectManager.. here is where my DbContext was getting cross-threaded */ .Then(lastContext => { Console.WriteLine(); bool wroteConcreteMsg = false; if (null != lastContext && null != lastContext.Workflow && null != lastContext.Workflow.Data) { MySpecialObjectInterviewPassThroughData castItem = lastContext.Workflow.Data as MySpecialObjectInterviewPassThroughData; if (null != castItem) { Console.WriteLine("MySpecialObjectInterviewDefaultWorkflow complete :) {0} -> {1}", castItem.PropertyOne, castItem.PropertyTwo); wroteConcreteMsg = true; } } if (!wroteConcreteMsg) { Console.WriteLine("MySpecialObjectInterviewDefaultWorkflow complete (.Data did not cast)"); } return ExecutionResult.Next(); })) .OnError(WorkflowCore.Models.WorkflowErrorHandling.Retry, TimeSpan.FromSeconds(60)); } } }
e
ICollection<string> workFlowGeneratedIds = new List<string>(); for (int i = 0; i < 10; i++) { MySpecialObjectInterviewPassThroughData currentMySpecialObjectInterviewPassThroughData = new MySpecialObjectInterviewPassThroughData(); currentMySpecialObjectInterviewPassThroughData.MySpecialObjectInterviewPassThroughDataSurrogateKey = i; //// private readonly IWorkflowHost workflowHost; string wfid = await this.workflowHost.StartWorkflow(MySpecialObjectInterviewDefaultWorkflow.WorkFlowId, MySpecialObjectInterviewDefaultWorkflow.WorkFlowVersion, currentMySpecialObjectInterviewPassThroughData); workFlowGeneratedIds.Add(wfid); }
fonte
No meu caso, eu uso um componente de modelo no Blazor.
<BTable ID="Table1" TotalRows="MyList.Count()">
O problema é chamar um método (Count) no cabeçalho do componente. Para resolver o problema, mudei assim:
int total = MyList.Count();
e depois :
<BTable ID="Table1" TotalRows="total">
fonte
Sei que esse problema foi perguntado há dois anos, mas eu simplesmente tive esse problema e a correção que usei realmente ajudou.
Se você estiver fazendo duas consultas com o mesmo Contexto - pode ser necessário remover o
AsNoTracking
. Se você usar,AsNoTracking
estará criando um novo leitor de dados para cada leitura. Dois leitores de dados não podem ler os mesmos dados.fonte
No meu caso eu estava usando um bloqueio que não permite o uso de await e não cria um aviso do compilador quando você não espera um async.
O problema:
lock (someLockObject) { // do stuff context.SaveChangesAsync(); } // some other code somewhere else doing await context.SaveChangesAsync() shortly after the lock gets the concurrency error
A correção: aguarde o assíncrono dentro do bloqueio, tornando-o bloqueado com um .Wait ()
lock (someLockObject) { // do stuff context.SaveChangesAsync().Wait(); }
fonte
Outro caso possível: se você usa a conexão direta, não esqueça de fechar se. Eu precisava executar uma consulta SQL arbitrária e ler o resultado. Esta foi uma solução rápida, eu não queria definir uma classe de dados, não configurar uma conexão SQL "normal". Então, simplesmente reutilizei a conexão de banco de dados do EFC como
var connection = Context.Database.GetDbConnection() as SqlConnection
. Certifique-se de ligarconnection.Close()
antes de fazerContext.SaveChanges()
.fonte
Tive o mesmo problema quando tento usar
FirstOrDefaultAsync()
o método assíncrono no código abaixo. E quando eu conserteiFirstOrDefault()
- o problema foi resolvido!_context.Issues.Add(issue); await _context.SaveChangesAsync(); int userId = _context.Users .Where(u => u.UserName == Options.UserName) .FirstOrDefaultAsync() .Id; ...
fonte
Se o seu método está retornando algo de volta, você pode resolver esse erro colocando
.Result
no final do trabalho e.Wait()
se não retornar nada.fonte
Eu apenas consegui fazer funcionar novamente. Não faz muito sentido, mas funcionou:
Vou investigar mais tarde, mas o método que chamei com hangfire recebe um DBContext e essa é a causa possível.
fonte