EF Core - вторая операция началась в этом контексте перед завершением предыдущей операции.Любые члены экземпляра не гарантированно являются потокобезопасными - PullRequest
0 голосов
/ 23 февраля 2019

Я использую 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 голосов
/ 24 февраля 2019

Вы можете получить несколько экземпляров 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

In Startup.cs:

services.AddDbContext<YourDbContext>(
    ServiceLifetime.Transient,
    options =>  options.UseSqlServer(Configuration.GetConnectionString("SomeConnectionStringName")));
0 голосов
/ 23 февраля 2019

Вы не можете этого сделать.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 происходит в рамках транзакции)

...