实体框架核心:在上一个操作完成之前,在此上下文中启动了第二个操作

And*_*uiz 42 c# entity-framework asp.net-web-api

我正在使用Entity Framework Core开发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"/>
Run Code Online (Sandbox Code Playgroud)

在我的一个列表方法中,我收到此错误:

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()
Run Code Online (Sandbox Code Playgroud)

这是我的方法:

    [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;
    }
Run Code Online (Sandbox Code Playgroud)

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

我运行了一个Hangfire作业,但是这个作业不使用同一个实体.这就是我能想到的所有相关内容.有什么可能导致这个的想法?

als*_*ami 45

我不确定您是否使用IoC和依赖注入来解决您可能使用的DbContext.如果您这样做并且您正在使用来自.NET Core(或任何其他IoC-Container)的本机IoC,并且您收到此错误,请确保将DbContext注册为Transient.做

services.AddTransient<MyContext>();
Run Code Online (Sandbox Code Playgroud)

要么

services.AddDbContext<MyContext>(ServiceLifetime.Transient);
Run Code Online (Sandbox Code Playgroud)

代替

services.AddDbContext<MyContext>();
Run Code Online (Sandbox Code Playgroud)

AddDbContext将上下文添加为作用域,这可能会在处理多个线程时引起麻烦.

此外异步/等待操作可以通过异步lambda表达式时,会导致此行为.

将其添加为瞬态也有其缺点.您将无法在使用上下文的多个类上对某个实体进行更改,因为每个类都将获得自己的DbContext实例.

  • 如前所述,如果您错过了使用 await 关键字调用异步方法,您将面临这个问题。 (4认同)
  • @alsami你是我的英雄。6个小时的痛苦调试。这就是解决方案。如果其他人将 IHttpContextAccessor 注入 DbContext 并且声明为 null,则这就是解决方案。非常感谢你,伙计。 (3认同)
  • 当我使用瞬态时,我收到以下连接错误(关闭或处理)'OmniService.DataAccess.Models.OmniServiceDbContext'。System.ObjectDisposedException:无法访问已处理的对象。此错误的一个常见原因是处理从依赖注入解析的上下文,然后尝试在应用程序的其他地方使用相同的上下文实例。如果您在上下文上调用 Dispose() 或将上下文包装在 using 语句中,则可能会发生这种情况。... 对象名称:'AsyncDisposer'。 (2认同)
  • 嗨@David!我猜你正在使用`Task.Run(async()=> context.Set ...)`而不等待它或创建一个范围的数据库上下文而不等待结果.这意味着您的上下文可能在访问时已被处理掉.如果您使用的是Microsoft DI,则必须在"Task.Run"中自己创建依赖项.查看这些链接./sf/ask/3153351421/ https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.iservicescopefactory.createscope?view = aspnetcore-2.1 (2认同)
  • 这可能没问题,但必须更加考虑数据访问的所需生命周期和分辨率范围,而不是在不考虑具体情况的情况下随意使用瞬态。事实上,我认为很少有人需要瞬态数据上下文。如果工作单元的范围涉及多个数据操作,则事务范围应该不止于此。数据上下文的解析应该反映您的工作单元的范围。这是应该深思熟虑的事情,这并不是一个一刀切的答案。 (2认同)
  • @AndreLuz这是正确的答案,应该被接受。无论如何,它为阿尔萨米赢得了“无名英雄”奖。 (2认同)
  • @jcmontx“如果其他人将 IHttpContextAccessor 注入 DbContext”,那么他们正在做**非常非常**错误的事情,并且应该受到他们所遭受的任何痛苦。 (2认同)

Gab*_*uci 31

该异常意味着_context两个线程同时使用该异常; 同一请求中的两个线程,或两个请求.

_context声明的静态可能吗?它不应该.

或者您是否GetClients在代码中的其他位置的同一请求中多次调用?

您可能已经这样做,但理想情况下,你会使用依赖注入你的DbContext,这意味着你可以使用AddDbContext()你的Startup.cs,和你的控制器构造将是这个样子:

private readonly MyDbContext _context; //not static

public MyController(MyDbContext context) {
    _context = context;
}
Run Code Online (Sandbox Code Playgroud)

如果您的代码不是这样的,请告诉我们,也许我们可以提供更多帮助.


小智 17

  • 使用 Startup.cs 文件中的这行代码解决我的问题。
    添加瞬态服务意味着每次请求服务时,在使用依赖注入时都会创建一个新实例

           services.AddDbContext<Context>(options =>
                            options.UseSqlServer(_configuration.GetConnectionString("ContextConn")),
                 ServiceLifetime.Transient);
    
    Run Code Online (Sandbox Code Playgroud)

  • 这种方法在多用户即时交易数量较多的情况下会出现问题。 (4认同)
  • 这正是我在帖子中所说的。 (2认同)

DiP*_*Pix 15

我遇到了同样的问题,结果发现父服务是一个单身人士。所以上下文也自动变成了单例。即使在 DI 中被声明为 Per Life Time Scoped。

将具有不同生命周期的服务注入另一个

  1. 永远不要将 Scoped & Transient 服务注入 Singleton 服务。(这有效地将瞬态或范围服务转换为单例。)

  2. 永远不要将瞬态服务注入范围服务(这会将瞬态服务转换为范围服务。)


Ham*_*loo 13

在某些情况下,此错误是由这种情况引起的:您调用异步方法,但在调用该方法之前未使用await。我的问题通过在该方法之前添加等待来解决。但是答案可能与所提到的问题无关,但可以帮助解决类似的错误。

  • 这发生在我身上。将 `First()` 更改为 `await / FirstAsync()` 有效。 (9认同)

小智 8

我有同样的错误。发生这种情况是因为我调用了一个构造为public async void ...而不是public async Task ....


小智 8

我认为这个答案仍然可以帮助某人并节省很多次。我通过更改IQueryableList(或更改为数组、集合...)解决了类似的问题。

例如:

var list=_context.table1.where(...);
Run Code Online (Sandbox Code Playgroud)

var list=_context.table1.where(...).ToList(); //or ToArray()...
Run Code Online (Sandbox Code Playgroud)

  • 恕我直言,这个答案不值得扣分,它只是表达得不好。.ToList() 确实解决了“第二次操作...”的大多数问题,因为它强制立即评估表达式。这样就没有排队上下文操作。 (4认同)
  • 这就是我的案例中的问题。我在查询的 where 子句中有 xxx.Contains(z.prop) 。xxx 应该是从早期查询解析的不同 int[] 数组。不幸的是,当第二个查询命中时,xxx 仍然是 IQueryable。在第二个查询之前添加 xxx.ToArray() 解决了我的问题。 (2认同)

NiP*_*Pfi 8

我有一个后台服务,可以对表中的每个条目执行操作。问题是,如果我迭代并修改 DbContext 同一实例上的某些数据,则会发生此错误。

正如本线程中提到的,一种解决方案是通过如下定义将 DbContext 的生命周期更改为瞬态:

services.AddDbContext<DbContext>(ServiceLifetime.Transient);
Run Code Online (Sandbox Code Playgroud)

但因为我在多个不同的服务中进行了更改,并使用该方法立即提交它们,所以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);
   } 
}
Run Code Online (Sandbox Code Playgroud)

能够像一个简单的请求一样使用该服务。因此,为了解决这个问题,我只是将单个作用域分成两个,一个用于查询,另一个用于写入操作,如下所示:

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);
   } 
}
Run Code Online (Sandbox Code Playgroud)

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

另一种可能的解决方案是确保读取操作在开始迭代之前已终止。在我的例子中,这不太实用,因为可能有很多结果都需要加载到内存中才能进行操作,而我首先试图通过使用 Queryable 来避免这种情况。


小智 8

Entity Framework Core 不支持在同一DbContext实例上运行多个并行操作。这包括async查询的并行执行和来自多个线程的任何显式并发使用。因此,始终await async立即调用,或者DbContext为并行执行的操作使用单独的实例。


Moh*_*raf 5

我遇到了同样的问题,因为我使用的是 Task.WhenAll,并且 DbContext 实例不是线程安全的,因此您可以为每个并行操作创建一个新的 DbContext。

var results = await Task.WhenAll(requests.Select(async request =>
    {
        using (var dbContext = dbContextFactory.CreateDbContext())
        {
            var result = await InsertRequestIntoDatabase(request, dbContext);
            return result;
        }
    }));
Run Code Online (Sandbox Code Playgroud)