Usando transações ou SaveChanges (falsos) e AcceptAllChanges ()?
-
03-07-2019 - |
Pergunta
Eu têm vindo a investigar transações e parece que eles cuidar de si na EF, enquanto eu passar false
para SaveChanges()
e depois chamar AcceptAllChanges()
se não há erros:
SaveChanges(false);
// ...
AcceptAllChanges();
E se algo vai mal? Não tenho a reversão ou, tão logo o meu método sai do escopo, é a operação terminou?
O que acontece com as colunas indentiy que foram atribuídos a meio da transação? Eu presumo que se alguém adicionou um registro após o meu antes meu foi ruim, então isso significa que haverá um valor de identidade faltando.
Existe alguma razão para usar a classe TransactionScope
padrão no meu código?
Solução
Com o Entity Framework a maior parte do SaveChanges()
tempo é suficiente. Isso cria uma transação, ou pede em qualquer transação de ambiente, e faz todo o trabalho necessário em que transação.
Às vezes, porém o emparelhamento SaveChanges(false) + AcceptAllChanges()
é útil.
O lugar mais útil para isso é em situações onde você quer fazer uma transação distribuída em dois contextos diferentes.
i. algo assim (mal):
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();
}
Se context1.SaveChanges()
tiver êxito, mas context2.SaveChanges()
falhar toda a transação distribuída é abortada. Mas, infelizmente, o Entity Framework já descartadas as alterações no context1
, para que você não pode repetir ou efetivamente registrar a falha.
Mas se você alterar seu código para olhar como esta:
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();
}
Enquanto a chamada para SaveChanges(false)
envia os comandos necessários para o banco de dados, o próprio contexto não é alterado, para que possa fazê-lo novamente, se necessário, ou você pode interrogar o ObjectStateManager
se quiser.
Isto significa que se a transação realmente gera uma exceção você pode compensar, por qualquer re-tentar ou registro de estado de cada contextos ObjectStateManager
em algum lugar.
Outras dicas
Se você estiver usando EF6 (Entity Framework 6+), isso mudou para chamadas de banco de dados para SQL.
Veja: http://msdn.microsoft.com/en-us/data/dn456843.aspx
uso context.Database.BeginTransaction.
De 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 } } }
Porque algum banco de dados pode lançar uma exceção em dbContextTransaction.Commit () então é melhor isto:
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();
}
}
}