我正在做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作业,但这个作业不使用相同的实体。这就是我能想到的所有相关信息。知道是什么引起的吗?


当前回答

将以下代码放入.csproject文件中并纠正所有错误

  <PropertyGroup>
     <WarningsAsErrors>CS4014</WarningsAsErrors>
  </PropertyGroup>

这段代码强制您使用await for异步方法

其他回答

在某些情况下,当调用没有await关键字的异步方法时,会发生此错误,这可以通过在方法调用之前添加await来简单地解决。然而,答案可能与上述问题无关,但它可以帮助解决类似的错误。

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

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

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

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

private readonly MyDbContext _context; //not static

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

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

我设法通过将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()

我不确定你是否使用IoC和依赖注入来解决你的DbContext可能被使用的地方。如果你使用了。net Core(或任何其他IoC- container)的原生IoC,并且你得到了这个错误,请确保将DbContext注册为Transient。做

services.AddDbContext<MyContext>(ServiceLifetime.Transient);

OR

services.AddTransient<MyContext>();

而不是

services.AddDbContext<MyContext>();

AddDbContext将上下文作为作用域添加,这在处理多线程时可能会带来麻烦。

当使用异步lambda表达式时,async / await操作也会导致这种行为。

将其添加为瞬态也有其缺点。你不能在多个使用上下文的类上修改某个实体,因为每个类都有自己的DbContext实例。

对此的简单解释是,DbContext实现不是线程安全的。你可以在这里阅读更多相关内容

我有一个后台服务,它为表中的每个条目执行一个操作。问题是,如果我在DbContext的同一个实例上遍历并修改一些数据,就会出现这个错误。

一个解决方案,正如在这个线程中提到的,是将DbContext的生存期更改为transient,方法是将它定义为

services.AddDbContext<DbContext>(ServiceLifetime.Transient);

但是因为我在多个不同的服务中做更改,并使用SaveChanges()方法一次性提交它们,这个解决方案在我的情况下不起作用。

因为我的代码是在服务中运行的,所以我做的事情就像

using (var scope = Services.CreateScope())
{
   var entities = scope.ServiceProvider.GetRequiredService<IReadService>().GetEntities();
   var writeService = scope.ServiceProvider.GetRequiredService<IWriteService>();
   foreach (Entity entity in entities)
   {
       writeService.DoSomething(entity);
   } 
}

能够像使用一个简单的请求一样使用服务。所以为了解决这个问题,我把一个作用域分成了两个,一个用于查询,另一个用于写操作,就像这样:

using (var readScope = Services.CreateScope())
using (var writeScope = Services.CreateScope())
{
   var entities = readScope.ServiceProvider.GetRequiredService<IReadService>().GetEntities();
   var writeService = writeScope.ServiceProvider.GetRequiredService<IWriteService>();
   foreach (Entity entity in entities)
   {
       writeService.DoSomething(entity);
   } 
}

像这样,实际上使用了两个不同的DbContext实例。

另一种可能的解决方案是确保在开始迭代之前读取操作已经终止。这在我的情况下不是很实际,因为可能有很多结果,都需要加载到内存的操作,我试图避免使用一个可查询的摆在首位。