【问题标题】:EF Core - A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safeEF Core - 在前一个操作完成之前在此上下文上启动了第二个操作。不保证任何实例成员都是线程安全的
【发布时间】:2019-02-23 16:49:27
【问题描述】:

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

我该如何解决这个问题?

【问题讨论】:

  • 您真的需要异步更改实体状态吗?它对我来说没有意义。
  • 更改 IsModified 不会长期运行!
  • 我想多次致电SaveChanges 不是一个好主意。最好更新所有实体并调用一次。

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


【解决方案1】:

你不能那样做。 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 内的操作发生在事务范围内)

【讨论】:

  • 但我没有收到任何错误,也没有反映在 Db 中的值
  • 您是否对模型进行了更改? :IsModify 只会强制更新模型,但如果之前的值不变,您将看到与更新后相同的数据
  • 是的。在调试时,我添加了快速监视并检查出勤的值,我看到了所需的值
  • 为什么要手动设置呢?实体是否处于分离状态(即通过 WebApi 调用)?通常 EF Core 在从数据库中获取时会自动跟踪更改(并且没有明确禁用跟踪)
  • 对不起!我没有得到你。但在这里添加细节。在一个请求中,我正在获取 Db 实体,进行一些 BL 和考勤属性更新,然后需要保存它
【解决方案2】:

您可以获得多个 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 生命周期(默认作用域)更改为 trasient

在Startup.cs:

services.AddDbContext<YourDbContext>(
    ServiceLifetime.Transient,
    options =>  options.UseSqlServer(Configuration.GetConnectionString("SomeConnectionStringName")));

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-11-06
    • 2018-11-07
    • 2019-05-25
    • 1970-01-01
    • 2018-07-23
    • 1970-01-01
    • 2020-09-30
    • 2020-05-11
    相关资源
    最近更新 更多