首先,(至少)支持 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) {
/* 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);
}
}
和
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 积累。 (同样,这是针对控制台应用程序(命令行界面)...其行为与 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;
}
让我感到惊讶的是(更改为)瞬态
.AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>()
.AddTransient<IMySpecialObjectManager, MySpecialObjectManager>()
注意,我认为因为 IMySpecialObjectManager 被注入到“MySpecialObjectThatSpawnsThreads”中,所以那些注入的对象需要是瞬态的才能完成链。
重点是....... 需要 .Transient 的不仅仅是 (My)DbContext...而是更大的 DI 图表块。
调试提示:
这一行:
this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
把你的调试器断点放在那里。如果您的 MySpecialObjectThatSpawnsThreads 正在创建 N 个线程(例如 10 个线程)......并且该行只被击中一次......那是你的问题。您的 DbContext 正在跨线程。
奖金:
我建议阅读下面的 url/article(oldie but goodie),了解 web-apps 和 console-apps 的区别
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));
}
}
}
和
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);
}