Stimmen Sie zuerst (zumindest) alsamis Antwort ab. Das hat mich auf den richtigen Weg gebracht.
Aber für diejenigen unter Ihnen, die IoC machen, ist hier ein etwas tieferer Tauchgang.
Mein Fehler (wie bei anderen)
Ein oder mehrere Fehler sind aufgetreten. (Eine zweite Operation wurde in diesem Kontext gestartet, bevor eine vorherige Operation abgeschlossen wurde. Dies wird normalerweise durch verschiedene Threads verursacht, die dieselbe Instanz von DbContext verwenden. Weitere Informationen zum Vermeiden von Threading-Problemen mit DbContext finden Sie unter
https://go.microsoft.com / fwlink /? linkid = 2097913. )
Mein Code-Setup. "Nur die Grundlagen" ...
public class MyCoolDbContext: DbContext{
public DbSet <MySpecialObject> MySpecialObjects { get; set; }
}
und
public interface IMySpecialObjectDomainData{}
und (Hinweis MyCoolDbContext wird injiziert)
public class MySpecialObjectEntityFrameworkDomainDataLayer: IMySpecialObjectDomainData{
public MySpecialObjectEntityFrameworkDomainDataLayer(MyCoolDbContext context) {
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
}
}
und
public interface IMySpecialObjectManager{}
und
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);
}
}
Und schließlich meine Multithread-Klasse, die von einer Konsolen-App (Befehlszeilenschnittstellen-App) aufgerufen wird.
public interface IMySpecialObjectThatSpawnsThreads{}
und
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);
}
}
und der DI-Aufbau. (Auch dies gilt für eine Konsolenanwendung (Befehlszeilenschnittstelle) ... die sich geringfügig von Web-Apps unterscheidet.)
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;
}
Diejenigen, die mich überraschten, waren die (Wechsel zu) vergänglich für
.AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>()
.AddTransient<IMySpecialObjectManager, MySpecialObjectManager>()
Hinweis: Da IMySpecialObjectManager in "MySpecialObjectThatSpawnsThreads" injiziert wurde, mussten diese injizierten Objekte vorübergehend sein, um die Kette zu vervollständigen.
Der Punkt ist ....... es war nicht nur der (My) DbContext, der benötigt wurde .Transient ... sondern ein größerer Teil des DI-Graphen.
Debugging-Tipp:
Diese Linie:
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
Setzen Sie dort Ihren Debugger-Haltepunkt. Wenn Ihr MySpecialObjectThatSpawnsThreads N Threads erstellt (z. B. 10 Threads) ... und diese Zeile nur einmal getroffen wird ... ist das Ihr Problem. Ihr DbContext kreuzt Threads.
BONUS:
Ich würde vorschlagen, diese unten stehende URL / Artikel (Oldie but Goodie) über die Unterschiede zwischen Web-Apps und Konsolen-Apps zu lesen
https://mehdi.me/ambient-dbcontext-in-ef6/
Hier ist die Kopfzeile des Artikels, falls sich der Link ändert.
DBCONTEXT MIT ENTITY FRAMEWORK 6 RICHTIG VERWALTEN: EIN TIEFENFÜHRER Mehdi El Gueddari
Ich habe dieses Problem mit WorkFlowCore festgestellt https://github.com/danielgerlag/workflow-core
<ItemGroup>
<PackageReference Include="WorkflowCore" Version="3.1.5" />
</ItemGroup>
Beispielcode unten .. um zukünftigen Internet-Suchern zu helfen
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));
}
}
}
und
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);
}