Использование транзакций или 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();
}
}
}