我正在做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作业,但这个作业不使用相同的实体。这就是我能想到的所有相关信息。知道是什么引起的吗?
我有一个后台服务,它为表中的每个条目执行一个操作。问题是,如果我在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实例。
另一种可能的解决方案是确保在开始迭代之前读取操作已经终止。这在我的情况下不是很实际,因为可能有很多结果,都需要加载到内存的操作,我试图避免使用一个可查询的摆在首位。
我有一个后台服务,它为表中的每个条目执行一个操作。问题是,如果我在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实例。
另一种可能的解决方案是确保在开始迭代之前读取操作已经终止。这在我的情况下不是很实际,因为可能有很多结果,都需要加载到内存的操作,我试图避免使用一个可查询的摆在首位。
为这个错误添加另一个可能的解决方案,以防它帮助到某人。
在我的情况下,问题是在查询中使用nav属性,就像这样:
var selectedOrder = dbContext.Orders.Where(x => x.Id == id).Single();
var relatedOrders = dbContext.Orders.Where(x => x.User.Id == selectedOrder.User.Id).ToList();
问题是在查询中使用selectedOrder.User.Id。如果User nav属性还没有被加载,EF将在试图执行查询的过程中尝试延迟加载该属性,它认为这是试图开始第二个操作。解决方案是为selectedOrder.User创建一个单独的变量。Id,以确保在查询开始之前加载了查询所需的信息:
var selectedOrder = dbContext.Orders.Where(x => x.Id == id).Single();
var userId = selectedOrder.User.Id;
var relatedOrders = dbContext.Orders.Where(x => x.User.Id == userId).ToList();