2015年11月18日

EntityFramework(18):トランザクション処理

  
EntityFrameworkでトランザクションを利用するには、以下の3種類の方法があります。

  1. 暗黙のトランザクションを利用する 
  2. BeginTransactionメソッドを利用する 
  3. TransactionScopeクラスを利用する 


■ 暗黙のトランザクションを利用する

SaveChangesが呼び出されると、EntityFrameworkは、呼び出しがトランザクション のスコープにあるかどうかを判断し、トランザクションのスコープに無い場合に、 自動的にローカルトランザクションを開始し、生成したSQL コマンドを実行しま す。
すべての SQL コマンドが終了すると、EntityFramework はローカル トランザク ションをコミットして戻ります。
これで十分な場面はけっこうあると思います。


■ BeginTransactionメソッドを利用する

データベースからデータを取得し、何らかの加工をしてから、データを更新する ようなシチュエーションで利用します。
ここでは、面倒なので、単にINSERTするだけのコードにします。

 using (var db = new NorthwindContext()) {
     db.SetLogging();
     using (var tx = db.Database.BeginTransaction(System.Data.IsolationLevel.Serializable)) {
         try {
             Category cat = new Category { CategoryName = "New Category" };
             Product product = new Product { ProductName = "New Product", Category = cat };
             db.Products.Add(product);
             db.SaveChanges();
             tx.Commit();
         } catch (DbUpdateException ex) {
             tx.Rollback();
         }
     }
 }

Commit とか、Rollbackとは書かないといけないのが面倒ですね。
でも、EntityFrameworkで明示的にトランザクションをかけたい時は、この方法が標準でしょうか?


■ TransactionScopeクラスを利用する

本来の使い方は、「複数のデータベースに対する読み書き処理」を ひとつのトランザクションに束ねるために利用するものです。
でも、一つのデータベースしか使わない場合は、MS-DTCの昇格は行われません。 なので、暗黙のトランザクションを利用するメソッドが複数あって、 これを束ねて一つのトランザクションとして処理したい場合にも利用できます。 これ使えば実装の柔軟性が随分とあがるというのが個人的な感想です。
以下、コードを載せます。

 using (var tx = new TransactionScope()) {
     using (var db = new NorthwindContext()) {
         db.SetLogging();
         Category cat = new Category { CategoryName = "New Category" };
         db.Categories.Add(cat);
         db.SaveChanges();
     }
     using (var db = new NorthwindContext()) {
         db.SetLogging();
         Category cat = db.Categories.First(x => x.CategoryName == "New Category");
         Product product = new Product { ProductName = "New Product", Category = cat };
         db.Products.Add(product);
         db.SaveChanges();
     }
     tx.Complete();
 }

Complete() 忘れると、コミットされないので注意してください。なお、Rollbackは勝手にやってくれます。



この記事へのコメント
トランザクションについて質問させてください。
上記コードではcatchで明示的にrollbackを指示しておりますが、
トランザクション自体が例外でusingを抜ける際のDispose内でrollbackは呼ばれないのでしょうか?
よろしくお願いします。
Posted by やま at 2017年11月10日 00:24
コメント、ありがとうございます。
普通は、暗黙のトランザクション利用すれば良いと思います。この場合は、usingすれば、Rollbackは不要です、

ご指摘のDatabase.BeginTransactionを使った明示的なトランザクションでは、Rollbackが不要では?という点ですが、以下のMicrosoftのサイトには、Rollbackを呼び出すサンプルが載っています。
https://msdn.microsoft.com/en-us/library/dn456843(v=vs.113).aspx

僕の英語力だとはっきりとは言えないのですが、Roolbak不要とは書かれていません。これについての正確なAPIドキュメントが探せませんでした。


Posted by Gushwell at 2017年11月10日 21:01
リンク先のmicrosoftのサンプルを確認しました。
たしかに明示的にrollbackが記載されています。

調査にお手間をかけて頂き、ありがとうございました。
Posted by やま at 2017年11月10日 23:35