EF核心-在此之前的操作完成之前,在此上下文中启动了第二个操作。不保证任何实例成员都是线程安全的

时间:2019-02-23 16:49:28

标签: c# asp.net-core entity-framework-core ef-core-2.1

我正在使用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>();

我该如何解决?

2 个答案:

答案 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")));