첫째, (적어도) alsami의 대답에 찬성 투표하십시오. 그것은 나를 올바른 길로 인도했습니다.
하지만 IoC를 수행하는 분들을 위해 여기에 좀 더 자세히 설명하겠습니다.
내 오류 (다른 사람과 동일)
하나 이상의 오류가 발생했습니다. (이전 작업이 완료되기 전에이 컨텍스트에서 두 번째 작업이 시작되었습니다. 이는 일반적으로 동일한 DbContext 인스턴스를 사용하는 여러 스레드로 인해 발생합니다. DbContext에서 스레딩 문제를 방지하는 방법에 대한 자세한 내용은 https://go.microsoft.com을 참조
하십시오. / fwlink /? linkid = 2097913. )
내 코드 설정. "그냥 기본"...
public class MyCoolDbContext: DbContext{
public DbSet <MySpecialObject> MySpecialObjects { get; set; }
}
과
public interface IMySpecialObjectDomainData{}
및 (MyCoolDbContext가 주입되고 있음에 유의하십시오)
public class MySpecialObjectEntityFrameworkDomainDataLayer: IMySpecialObjectDomainData{
public MySpecialObjectEntityFrameworkDomainDataLayer(MyCoolDbContext context) {
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
}
}
과
public interface IMySpecialObjectManager{}
과
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);
}
}
마지막으로 콘솔 앱 (명령 줄 인터페이스 앱)에서 호출되는 다중 스레드 클래스
public interface IMySpecialObjectThatSpawnsThreads{}
과
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);
}
}
그리고 DI 빌드 업. (다시 말하지만 이것은 콘솔 응용 프로그램 (명령 줄 인터페이스) 용입니다 ... 웹 응용 프로그램과 약간 다른 동작을 나타냄)
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;
}
나를 놀라게 한 것은
.AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>()
.AddTransient<IMySpecialObjectManager, MySpecialObjectManager>()
IMySpecialObjectManager가 "MySpecialObjectThatSpawnsThreads"에 주입 되었기 때문에 주입 된 객체는 체인을 완성하기 위해 Transient가되어야한다고 생각합니다.
요점은 ..Transient를 필요로하는 (My) DbContext 뿐만이 아니라 DI 그래프의 더 큰 덩어리였습니다.
디버깅 팁 :
이 줄 :
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
디버거 중단 점을 거기에 두십시오. MySpecialObjectThatSpawnsThreads가 N 개의 스레드를 만들고 (예 : 10 개의 스레드) ... 그 줄이 한 번만 나오면 ... 그게 문제입니다. DbContext가 스레드를 교차하고 있습니다.
보너스:
web-apps와 console-apps의 차이점에 대해 아래 URL / 기사 (오래된 것이지만 goodie)를 읽는 것이 좋습니다.
https://mehdi.me/ambient-dbcontext-in-ef6/
링크가 변경된 경우 기사의 헤더입니다.
엔티티 프레임 워크를 사용하여 올바른 방법으로 DBCONTEXT 관리 6 : 심층 가이드 Mehdi El Gueddari
WorkFlowCore https://github.com/danielgerlag/workflow-core 에서이 문제가 발생했습니다.
<ItemGroup>
<PackageReference Include="WorkflowCore" Version="3.1.5" />
</ItemGroup>
아래의 샘플 코드 .. 미래의 인터넷 검색자를 돕기 위해
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));
}
}
}
과
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);
}