Синхронизация сохраненных изменений транзакций в EF

#c# #entity-framework

#c# #entity-framework

Вопрос:

Я хочу сохранить изменения в списке объектов Person в базе данных транзакционно. Я реализовал функцию, но я не знаю, нужно ли мне обернуть await Task.WhenAll(tasks); в TransactionScope или для ее получения уже достаточно моего кода.

     public class MyService {

        public MyContext Context { get; }

        public MyService(
            IDatabaseInitializer<MyContext> initializer
        ) {
            Context = new MyContext(initializer);
        }

        public async Task<int> AddOrUpdateDataAsync(IEnumerable<Person> persons)
        {
            List<Task> tasks = new List<Task>();
            try
            {
                foreach (Person person in persons)
                {
                    person.Status = Status.Finish;
                    person.Changed = DateTime.Now;
                    person.Role = Role.Worker;
                    MyContext.Persons.AddOrUpdate(person);
                    tasks.Add(await MyContext.SaveChangesAsync(););
                }

                await Task.WhenAll(tasks);
                return 1;
            }
            catch (EntityCommandExecutionException ex)
            {
                return 0;
            }
        }

    }
  

Ответ №1:

Если вы точно не уверены в том, что делаете, я не предлагаю вам обрабатывать исключение, возвращая 0. Я всегда предпочитаю обрабатывать исключение в самом внешнем стеке и регистрировать исключение, а затем предпринимать надлежащие действия. И если это возможно, я предлагаю вам использовать промежуточное программное обеспечение для обработки ошибок (это зависит от используемой вами платформы, используемого подхода, а также от ситуации).

С другой стороны, вам не нужно обновлять каждый объект один за другим. Вы можете вызвать SaveChangesAsync после того, как все операции выполнены.

 try
{
    foreach (Person person in persons)
    {
        person.Status = Status.Finish;
        person.Changed = DateTime.Now;
        person.Role = Role.Worker;
        MyContext.Persons.AddOrUpdate(person);
    }

    var affectedRows = await MyContext.SaveChangesAsync();
    return (int)(persons.Count == affectedRows);
}
catch (EntityCommandExecutionException ex)
{
    return 0;
}
  

Комментарии:

1. Является ли SaveChangesAsync атомарным? Я подумал, что мне следует объявить TransactionScope. Или мне нужен TransactionScope только в том случае, если я работаю с разными DataContexts?

2. SaveChangesAsync следует создать транзакцию в фоновом режиме, если она не задана вручную. Итак, ваш ответ — да.

3. небольшое отклонение, но OP вроде как наполовину выполняет что-то параллельно (запускает множество задач и ожидает их завершения с помощью WhenAll ()) — я полагаю, что на самом деле это не будет параллельно из-за способа работы SaveChangeAsync — но может ли это (например, имея отдельный контекстный объект db для каждого обновления)??

Ответ №2:

Все остальные ответы правильные, но не отвечают на ваш вопрос, является SaveChangesAsync атомарным или нет.
Согласно документации, ответ на ваш вопрос — ДА, как для EF6 , так и для «EF Core`:

Основные документы Ef:

По умолчанию, если поставщик базы данных поддерживает транзакции, все изменения в одном вызове SaveChanges() применяются в транзакции. Если какое-либо из изменений завершается неудачей, транзакция откатывается, и ни одно из изменений не применяется к базе данных. Это означает, что функция SaveChanges() гарантированно либо полностью завершит работу успешно, либо оставит базу данных неизмененной в случае возникновения ошибки.

Документы EF6 :

Во всех версиях Entity Framework всякий раз, когда вы выполняете SaveChanges() для вставки, обновления или удаления в базе данных, платформа преобразует эту операцию в транзакцию. Эта транзакция длится достаточно долго, чтобы выполнить операцию, а затем завершается. При выполнении другой подобной операции запускается новая транзакция.

Ответ №3:

 tasks.Add(await MyContext.SaveChangesAsync(););
  

должно быть без await

 tasks.Add(MyContext.SaveChangesAsync());
  

И ваш код начинает работать.
(выражение await возвращает асинхронный результат, без await возвращает задачу, которая будет выполнена позже)
Но, как упоминалось ранее, вы не должны выполнять SaveChangesAsync() каждый раз, когда объект добавляется в контекст, это может быть сделано для всех добавленных объектов за один раз.

Комментарии:

1. Да, это была неправильная tasks.Add(await MyContext.SaveChangesAsync();); ошибка копирования / вставки