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)
Ocorreram um ou mais erros. (Uma segunda operação foi iniciada neste contexto antes da conclusão de uma operação anterior. Isso geralmente é causado por threads diferentes usando a mesma instância de DbContext. Para obter mais informações sobre como evitar problemas de threading com DbContext, consulte
https://go.microsoft.com / fwlink /? linkid = 2097913. )
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) {
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) {
string defaultConnectionStringValue = string.Empty;
IServiceCollection servColl = new ServiceCollection()
.AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>()
.AddTransient<IMySpecialObjectManager, MySpecialObjectManager>()
# 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
.AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>()
.AddTransient<IMySpecialObjectManager, MySpecialObjectManager>()
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.
GERENCIANDO DBCONTEXT DA MANEIRA CERTA COM ENTIDADE FRAMEWORK 6: UM GUIA EM PROFUNDIDADE Mehdi El Gueddari
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();
})
.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;
string wfid = await this.workflowHost.StartWorkflow(MySpecialObjectInterviewDefaultWorkflow.WorkFlowId, MySpecialObjectInterviewDefaultWorkflow.WorkFlowVersion, currentMySpecialObjectInterviewPassThroughData);
workFlowGeneratedIds.Add(wfid);
}