Najpierw zagłosuj za (przynajmniej) odpowiedź alsami. To zaprowadziło mnie na właściwą ścieżkę.
Ale dla tych z was, którzy zajmują się IoC, tutaj jest trochę głębsze nurkowanie.
Mój błąd (taki sam jak inne)
Wystąpił jeden lub więcej błędów. (Druga operacja została uruchomiona w tym kontekście przed zakończeniem poprzedniej operacji. Jest to zwykle spowodowane przez różne wątki korzystające z tego samego wystąpienia DbContext. Aby uzyskać więcej informacji na temat unikania problemów z wątkami w przypadku DbContext, zobacz
https://go.microsoft.com / fwlink /? linkid = 2097913. )
Moja konfiguracja kodu. „Tylko podstawy” ...
public class MyCoolDbContext: DbContext{
public DbSet <MySpecialObject> MySpecialObjects { get; set; }
}
i
public interface IMySpecialObjectDomainData{}
i (uwaga MyCoolDbContext jest wstrzykiwany)
public class MySpecialObjectEntityFrameworkDomainDataLayer: IMySpecialObjectDomainData{
public MySpecialObjectEntityFrameworkDomainDataLayer(MyCoolDbContext context) {
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
}
}
i
public interface IMySpecialObjectManager{}
i
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);
}
}
I wreszcie moja wielowątkowa klasa wywoływana z aplikacji konsoli (aplikacja interfejsu wiersza poleceń)
public interface IMySpecialObjectThatSpawnsThreads{}
i
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);
}
}
i budowanie DI. (Ponownie dotyczy to aplikacji konsoli (interfejsu wiersza poleceń) ... która wykazuje nieco inne zachowanie niż aplikacje internetowe)
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;
}
Te, które mnie zaskoczyły, to (zmiana na) przejściowe dla
.AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>()
.AddTransient<IMySpecialObjectManager, MySpecialObjectManager>()
Uwaga, myślę, że ponieważ IMySpecialObjectManager był wstrzykiwany do „MySpecialObjectThatSpawnsThreads”, te wstrzyknięte obiekty musiały być przejściowe, aby zakończyć łańcuch.
Chodziło o to, że ....... potrzebował nie tylko (My) DbContext .Transient ... ale większy fragment Di Graph.
Wskazówka dotycząca debugowania:
Ta linia:
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
Umieść tam punkt przerwania debugera. Jeśli Twój MySpecialObjectThatSpawnsThreads tworzy N liczbę wątków (na przykład 10 wątków) ...... i ta linia jest trafiona tylko raz ... to jest twój problem. Twój DbContext przecina wątki.
PREMIA:
Proponuję przeczytać poniższy adres url / artykuł (stary, ale dobry) o różnicach między aplikacjami internetowymi i aplikacjami konsolowymi
https://mehdi.me/ambient-dbcontext-in-ef6/
Oto nagłówek artykułu na wypadek zmiany linku.
WŁAŚCIWE ZARZĄDZANIE DBCONTEXT DZIĘKI RAMOM PODMIOTU 6: PRZEWODNIK POGŁĘBIONY Mehdi El Gueddari
Trafiłem na ten problem z WorkFlowCore https://github.com/danielgerlag/workflow-core
<ItemGroup>
<PackageReference Include="WorkflowCore" Version="3.1.5" />
</ItemGroup>
przykładowy kod poniżej .. aby pomóc przyszłym wyszukiwarkom internetowym
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));
}
}
}
i
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);
}