トランザクションまたはSaveChanges(false)およびAcceptAllChanges()を使用していますか?
-
03-07-2019 - |
質問
トランザクションを調査しており、 false
を SaveChanges()
に渡してから AcceptAllChangesを呼び出す限り、EFでトランザクションを処理しているようです。 ()
エラーがない場合:
SaveChanges(false);
// ...
AcceptAllChanges();
何かがうまくいかない場合はどうなりますか?ロールバックする必要はありませんか、メソッドがスコープから外れるとすぐにトランザクションは終了しますか?
トランザクションの途中で割り当てられたindentiy列はどうなりますか?もし私の誰かが私のものが悪くなる前に他の誰かがレコードを追加したなら、これはIdentityの値が欠けていることを意味すると思います。
標準の TransactionScope
クラスをコードで使用する理由はありますか?
解決
Entity Frameworkでは、ほとんどの場合 SaveChanges()
で十分です。これにより、トランザクションが作成されるか、アンビエントトランザクションに参加し、そのトランザクションで必要なすべての作業が行われます。
場合によっては、 SaveChanges(false)+ AcceptAllChanges()
のペアリングが便利です。
これに最も役立つ場所は、2つの異なるコンテキストで分散トランザクションを行いたい状況です。
つまりこのような(悪い):
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 } } }
一部のデータベースは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();
}
}
}