首先,给alsami的答案点赞(至少)。这让我走上了正确的道路。
但是对于那些做IoC的人来说,这里有一些更深入的研究。
我的错误(和其他人一样)
发生了一个或多个错误。(第二次行动就此展开
上一个操作完成前的上下文。这通常是由于
不同的线程使用相同的DbContext实例。更多的
有关如何使用DbContext避免线程问题的信息,请参见
https://go.microsoft.com/fwlink/?linkid=2097913)。
我的代码设置。“只是基本的”……
public class MyCoolDbContext: DbContext{
public DbSet <MySpecialObject> MySpecialObjects { get; set; }
}
and
public interface IMySpecialObjectDomainData{}
和(注意MyCoolDbContext正在被注入)
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);
}
}
and
public interface IMySpecialObjectManager{}
and
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{}
and
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的积累。(同样,这是一个控制台应用程序(命令行接口)…它表现出与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;
}
让我吃惊的是“transient for”的变化
.AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>()
.AddTransient<IMySpecialObjectManager, MySpecialObjectManager>()
注意,我认为因为IMySpecialObjectManager被注入到“MySpecialObjectThatSpawnsThreads”中,那些注入的对象需要是瞬态的才能完成链。
这一点是.......不只是(My)DbContext需要.Transient…而是DI图中更大的一块。
调试技巧:
这条线:
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
将调试器断点放在这里。如果你的MySpecialObjectThatSpawnsThreads正在制作N个线程(例如10个线程)......这条线只被击中一次…那是你的问题。你的DbContext正在跨线程。
奖金:
我建议你阅读下面这篇文章(很老但很好),关于网络应用程序和主机应用程序的区别
https://mehdi.me/ambient-dbcontext-in-ef6/
这里是文章的标题,以防链接发生变化。
使用实体框架6正确地管理dbcontext:深入
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();
})
/* 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));
}
}
}
and
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);
}