Использование транзакций или SaveChanges (false) и AcceptAllChanges ()?

346

Я изучал транзакции, и кажется, что они заботятся о себе в EF, пока я перехожу falseк, SaveChanges()а затем звоню, AcceptAllChanges()если нет ошибок:

SaveChanges(false);
// ...
AcceptAllChanges();

Что если что-то пойдет не так? мне не нужно откатывать или, как только мой метод выходит из области видимости, транзакция заканчивается?

Что происходит с какими-либо столбцами отступов, которые были назначены на полпути через транзакцию? Я предполагаю, что если кто-то еще добавил запись после моей, пока моя не испортилась, это означает, что будет отсутствовать значение Identity.

Есть ли причина использовать стандартный TransactionScopeкласс в моем коде?

Марк Смит
источник
1
Это помогло мне понять, почему SaveChanges(fase); ... AcceptAllChanges();был шаблон в первую очередь. Обратите внимание, как принятый ответ на вышеупомянутый вопрос написан автором блога - и на этот блог есть ссылка в другом вопросе. Это все вместе.
Красный горох

Ответы:

451

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

Иногда, хотя SaveChanges(false) + AcceptAllChanges()спаривание полезно.

Самое полезное место для этого - ситуации, когда вы хотите выполнить распределенную транзакцию в двух разных контекстах.

Т.е. как то так (плохо)

using (TransactionScope scope = new TransactionScope())
{
    //Do something with context1
    //Do something with context2

    //Save and discard changes
    context1.SaveChanges();

    //Save and discard changes
    context2.SaveChanges();

    //if we get here things are looking good.
    scope.Complete();
}

Если context1.SaveChanges()успешно, но context2.SaveChanges()не удается, вся распределенная транзакция прерывается. Но, к сожалению, Entity Framework уже отменил изменения context1, поэтому вы не можете воспроизвести или эффективно зарегистрировать ошибку.

Но если вы измените свой код, чтобы он выглядел так:

using (TransactionScope scope = new TransactionScope())
{
    //Do something with context1
    //Do something with context2

    //Save Changes but don't discard yet
    context1.SaveChanges(false);

    //Save Changes but don't discard yet
    context2.SaveChanges(false);

    //if we get here things are looking good.
    scope.Complete();
    context1.AcceptAllChanges();
    context2.AcceptAllChanges();

}

В то время как вызов SaveChanges(false)отправляет необходимые команды в базу данных, сам контекст не изменяется, поэтому вы можете сделать это снова, если необходимо, или вы можете опросить, ObjectStateManagerесли хотите.

Это означает, что если транзакция фактически выдает исключение, которое вы можете компенсировать, повторяя попытку или регистрируя состояние каждого контекста ObjectStateManagerгде-либо.

Смотрите мой блог для получения дополнительной информации.

Алекс Джеймс
источник
3
Это здорово, спасибо ... Так что, если что-то не получается, я не должен откат? SaveChanges помечает его как сохраненный, но фактически не фиксирует, пока я не сделаю acceptallchanges ... но если что-то пойдет не так ... мне понадобится откат, не так ли, чтобы мой объект вернулся в правильное состояние?
Марк Смит
33
@Mark: если вы имеете в виду «откат», вы возвращаете ваши объекты в состояние, в котором они находятся в базе данных, то нет, вы не захотите этого делать, потому что потеряете все пользовательские изменения в объектах. , SaveChanges(false)выполняет фактическое обновление базы данных, а AcceptAllChanges()EF говорит: «Хорошо, вы можете забыть, какие вещи нужно сохранить, потому что они были успешно сохранены». Если произойдет SaveChanges(false)сбой, AcceptAllChanges()он никогда не будет вызван, и EF все равно будет рассматривать ваш объект как имеющий свойства, которые были изменены и должны быть сохранены обратно в базу данных.
BlueRaja - Дэнни Пфлюгофт
Можете ли вы посоветовать, как сделать это с помощью Code First? У метода SaveChanges или AcceptAllChanges нет параметров
Кирстен Грид
2
Я задал вопрос об использовании этой техники с Code First здесь
Kirsten Greed
13
Это больше невозможно в EF 6.1. Знаете ли вы, какие корректировки необходимо сделать, чтобы работать сейчас?
Алекс Дреско
113

Если вы используете EF6 (Entity Framework 6+), это изменилось для вызовов базы данных в SQL.
См .: http://msdn.microsoft.com/en-us/data/dn456843.aspx

использовать context.Database.BeginTransaction.

Из MSDN:

using (var context = new BloggingContext()) 
{ 
    using (var dbContextTransaction = context.Database.BeginTransaction()) 
    { 
        try 
        { 
            context.Database.ExecuteSqlCommand( 
                @"UPDATE Blogs SET Rating = 5" + 
                    " WHERE Name LIKE '%Entity Framework%'" 
                ); 

            var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
            foreach (var post in query) 
            { 
                post.Title += "[Cool Blog]"; 
            } 

            context.SaveChanges(); 

            dbContextTransaction.Commit(); 
        } 
        catch (Exception) 
        { 
            dbContextTransaction.Rollback(); //Required according to MSDN article 
            throw; //Not in MSDN article, but recommended so the exception still bubbles up
        } 
    } 
} 
user3885816
источник
52
try-catch с roolback не требуется, когда вы используете «using» в транзакции.
Роберт
12
Я беру исключение, чтобы ловить исключение, как это. Это приводит к сбою операции базы данных. Из-за природы SO кто-то может взять этот пример и использовать его в производственном приложении.
B2K
3
@ B2K: Хороший вопрос, но этот код скопирован из связанной статьи Microsoft. Я надеюсь, что никто не использует их код в производстве :)
J Bryan Price
6
@Robert Согласно статье MSDN необходим Rollback (). Они специально пропускают команду Rollback для примера TransactionScope. @ B2K Я добавил в throw;фрагмент MSDN и четко указал, что это не оригинал из статьи MSDN.
Тодд
6
(Если правильно) Это может прояснить ситуацию: похоже, что EF + MSSQL не нуждается в откате, но EF + могут быть другими поставщиками SQL. Поскольку EF, как предполагается, не зависит от того, с какой базой данных он обращается, он Rollback()вызывается в случае, если он обращается к MySql или к чему-то, что не имеет такого автоматического поведения.
Слова,
-5

Поскольку некоторая база данных может выдать исключение в dbContextTransaction.Commit (), лучше это:

using (var context = new BloggingContext()) 
{ 
  using (var dbContextTransaction = context.Database.BeginTransaction()) 
  { 
    try 
    { 
      context.Database.ExecuteSqlCommand( 
          @"UPDATE Blogs SET Rating = 5" + 
              " WHERE Name LIKE '%Entity Framework%'" 
          ); 

      var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
      foreach (var post in query) 
      { 
          post.Title += "[Cool Blog]"; 
      } 

      context.SaveChanges(false); 

      dbContextTransaction.Commit(); 

      context.AcceptAllChanges();
    } 
    catch (Exception) 
    { 
      dbContextTransaction.Rollback(); 
    } 
  } 
} 
Эмель
источник
7
Я беру исключение, чтобы ловить исключение, как это. Это приводит к сбою операции базы данных. Из-за природы SO кто-то может взять этот пример и использовать его в производственном приложении.
B2K
6
Разве это не то же самое, что и другой ответ, который дал ссылку на страницу MSDN, которую он цитирует? Единственное различие , которое я вижу, что вы проходите falseв context.SaveChanges();, и дополнительно позвонить context.AcceptAllChanges();.
Вай Ха Ли
@ B2K откат не требуется - если транзакция не работает, ничего не фиксируется. Также явный вызов Rollback может быть неудачным - смотрите мой ответ здесь stackoverflow.com/questions/41385740/…
Кен
Откат не то, против чего я возражаю. Автор этого ответа обновил свой код, чтобы перебросить исключение, разрешив тем самым то, против чего я возражал.
B2K
Извините, я прокомментировал со своего телефона. Тодд повторно выдает исключение, а eMeL - нет. В подвохе должно быть что-то, уведомляющее разработчика или пользователя о проблеме, вызывающей откат. Это может быть запись в файл журнала, перезапуск исключения или возвращение сообщения пользователю.
B2K