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

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

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

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

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

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

Ответ 1

С 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 где-то.

Подробнее см. мой сообщение в блоге.

Ответ 2

Если вы используете 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
        } 
    } 
} 

Ответ 3

Обычно (в 99,9% случаев) исключения выполняются до или во время выполнения операторов sql. После этого AcceptAllChanges запускается, чтобы убедиться, что все утверждения завершены успешно.

DbContext SaveChanges автоматически вызывает метод (по крайней мере, в EF6), поэтому вам не нужно вызывать его напрямую. Единственное место, которое это полезно, - это когда вы разделили приложение в нескольких контекстах и ​​хотите сохранить как минимум 2 из них в транзакции. Если последнее сохранение завершилось неудачно, возможно, вы захотите повторить предыдущие сохранения.

Я советую использовать транзакции во все времена, если вы абсолютно не уверены, что ваше приложение не будет расти или добавить больше функциональности.

Ответ 4

Поскольку какая-то база данных может генерировать исключение в 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(); 
    } 
  } 
}