我正在使用EF Core 2.1
,并且尝试通过使用Tasks
(TPL)一次更新多个实体:
public async Task UpdateAttendance(IEnumerable<MyEvents> events)
{
try
{
var tasks = events.Select(async entity =>
{
await Task.Run(() => Context.Entry(entity).Property(x => x.Attendance).IsModified = true);
await Context.SaveChangesAsync();
});
await Task.WhenAll(tasks);
}
catch (Exception ex)
{
throw new Exception(ex.Message ?? ex.InnerException.Message);
}
}
但这会引发以下错误。
在先前的操作完成之前,第二个操作在此上下文上开始。不保证任何实例成员都是线程安全的。
Startup.cs
services.AddScoped<IMyRepository, MyRepository>();
我该如何解决?
答案 0 :(得分:2)
您不能那样做。 EF Core(与EF 6之前一样)不是线程安全的。
您必须等待任务,然后再开始下一个任务
var tasks = events.Select(async entity =>
{
await Task.Run(() => Context.Entry(entity).Property(x => x.Attendance).IsModified = true);
await Context.SaveChangesAsync();
});
await Task.WhenAll(tasks);
在这里,您正在并行启动多个任务并等待它们。您将不得不遍历它
foreach(var entity in events)
{
Context.Entry(entity).Property(x => x.Attendance).IsModified = true;
});
await Context.SaveChangesAsync();
并不确定为什么要逐个保存它(因此,该示例在调用SaveChangesAsync()
之前已更改为执行所有修改的标志),因为它效率很低。更新所有属性,然后最后运行SaveChanges更有意义,因为当您保存更改时,EF Core将保存所有更改/跟踪的实体。发生错误时也更容易回滚(SaveChangesAsync中的操作发生在事务范围内)
答案 1 :(得分:0)
您可以获得多个DbContext实例,有两种方法可以实现。
使用IServiceScopeFactory界面(推荐)
在MyRepository
中:
private readonly IServiceScopeFactory serviceScopeFactory;
public MyRepository(IServiceScopeFactory serviceScopeFactory)
{
this.serviceScopeFactory = serviceScopeFactory;
}
public async Task UpdateAttendance(IEnumerable<MyEvents> events)
{
try
{
var tasks = events.Select(async entity =>
{
await Task.Run(() =>
{
using (var scope = serviceScopeFactory.CreateScope())
{
var context = scope.GetRequiredService<YourContextType>();
context.Entry(entity).Property(x => x.Attendance).IsModified = true;
await Context.SaveChangesAsync();
}
});
});
await Task.WhenAll(tasks);
}
catch (Exception ex)
{
//You really should not do this, did you have a look to ILogger?
//If you only wants to stop when an exception gets thrown you can configure VS to do that, Debug menu -> Windows -> Exception Settings
throw new Exception(ex.Message ?? ex.InnerException.Message);
}
}
或将DbContext生存时间(默认范围内)更改为透明
在Startup.cs
中:
services.AddDbContext<YourDbContext>(
ServiceLifetime.Transient,
options => options.UseSqlServer(Configuration.GetConnectionString("SomeConnectionStringName")));