Использование транзакций или SaveChanges(false) и AcceptAllChanges()?
Я занимаюсь расследованием сделок, и кажется, что они заботятся о себе в EF, пока я прохожу false
в SaveChanges()
а затем позвоните AcceptAllChanges()
если нет ошибок:
SaveChanges(false);
// ...
AcceptAllChanges();
Что если что-то пойдет не так? мне не нужно откатывать или, как только мой метод выходит из области видимости, транзакция завершена?
Что происходит с любыми столбцами индентий, которые были назначены на полпути через транзакцию? Я предполагаю, что если кто-то еще добавил запись после моей, прежде чем моя испортилась, это означает, что будет отсутствовать значение Identity.
Есть ли основания использовать стандарт? TransactionScope
класс в моем коде?
4 ответа
С 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
где-то.
Если вы используете 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 } } }
public static TransactionScope CreateAsyncTransactionScope(IsolationLevel isolationLevel = IsolationLevel.ReadCommitted)
{
var transactionOptions = new TransactionOptions
{
/* IsolationLevel = isolationLevel,*/
Timeout = TransactionManager.MaximumTimeout,
};
return new TransactionScope(TransactionScopeOption.Required, transactionOptions, TransactionScopeAsyncFlowOption.Enabled);
}
если вы используете этот метод Проблема в уровне изоляции опции Scop транзакции, вы должны удалить эту опцию, если вы используете
Поскольку некоторая база данных может выдать исключение в 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();
}
}
}