我正在做ASP。Net Core 2.0项目使用实体框架核心

<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" />
  <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0"/>

在我的一个列表方法中,我得到了这个错误:

InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()

这是我的方法:

    [HttpGet("{currentPage}/{pageSize}/")]
    [HttpGet("{currentPage}/{pageSize}/{search}")]
    public ListResponseVM<ClientVM> GetClients([FromRoute] int currentPage, int pageSize, string search)
    {
        var resp = new ListResponseVM<ClientVM>();
        var items = _context.Clients
            .Include(i => i.Contacts)
            .Include(i => i.Addresses)
            .Include("ClientObjectives.Objective")
            .Include(i => i.Urls)
            .Include(i => i.Users)
            .Where(p => string.IsNullOrEmpty(search) || p.CompanyName.Contains(search))
            .OrderBy(p => p.CompanyName)
            .ToPagedList(pageSize, currentPage);

        resp.NumberOfPages = items.TotalPage;

        foreach (var item in items)
        {
            var client = _mapper.Map<ClientVM>(item);

            client.Addresses = new List<AddressVM>();
            foreach (var addr in item.Addresses)
            {
                var address = _mapper.Map<AddressVM>(addr);
                address.CountryCode = addr.CountryId;
                client.Addresses.Add(address);
            }

            client.Contacts = item.Contacts.Select(p => _mapper.Map<ContactVM>(p)).ToList();
            client.Urls = item.Urls.Select(p => _mapper.Map<ClientUrlVM>(p)).ToList();
            client.Objectives = item.Objectives.Select(p => _mapper.Map<ObjectiveVM>(p)).ToList();
            resp.Items.Add(client);
        }

        return resp;
    }

我有点迷失,特别是因为当我在本地运行它时,它可以工作,但当我部署到我的登台服务器(IIS 8.5)时,它会给我这个错误,并且它正常工作。在我增加了其中一个模型的最大长度后,错误开始出现。我还更新了相应视图模型的最大长度。还有很多类似的列表方法,它们都很有效。

我有一个正在运行的Hangfire作业,但这个作业不使用相同的实体。这就是我能想到的所有相关信息。知道是什么引起的吗?


当前回答

你可以使用SemaphoreSlim来阻止下一个尝试执行EF调用的线程。

static SemaphoreSlim semSlim = new SemaphoreSlim(1, 1);

await semSlim.WaitAsync();
try
{
  // something like this here...
  // EmployeeService.GetList(); or...
  var result = await _ctx.Employees.ToListAsync();
}
finally
{
  semSlim.Release();
}

其他回答

有一些Async函数,但是你没有在它之前调用await。在调用该函数之前查找并添加await将对您有所帮助。

首先,给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);
                }

实体框架核心不支持在同一个DbContext实例上运行多个并行操作。这既包括异步查询的并行执行,也包括来自多个线程的任何显式并发使用。因此,总是立即等待异步调用,或者为并行执行的操作使用单独的DbContext实例。

我设法通过将IQueryable传入一个方法,然后使用IQueryable“列表”作为另一个查询的一部分,以相同的上下文来获得这个错误。

public void FirstMethod()
{
    // This is returning an IQueryable
    var stockItems = _dbContext.StockItems
        .Where(st => st.IsSomething);

    SecondMethod(stockItems);
}

public void SecondMethod(IEnumerable<Stock> stockItems)
{
    var grnTrans = _dbContext.InvoiceLines
        .Where(il => stockItems.Contains(il.StockItem))
        .ToList();
}

为了阻止这种情况发生,我使用了这里的方法,并在传递第二个方法之前将该列表物化,通过将对SecondMethod的调用更改为SecondMethod(stockItems.ToList()

这个异常意味着_context同时被两个线程使用;同一个请求中的两个线程,或者由两个请求组成。

你的_context是否声明为静态?不应该如此。

或者您是否从代码中的其他地方在同一个请求中多次调用GetClients ?

你可能已经这样做了,但理想情况下,你会为你的DbContext使用依赖注入,这意味着你将在你的Startup.cs中使用AddDbContext(),你的控制器构造函数将看起来像这样:

private readonly MyDbContext _context; //not static

public MyController(MyDbContext context) {
    _context = context;
}

如果你的代码不是这样的,告诉我们,也许我们可以进一步帮助。