Visual Studio 2010 EF4 POCO Part3

お疲れさまです。もう少し頻繁にBlogを更新したいのですが、なかなか時間もとれず、更新できず申し訳ありません。今日はPOCOのChange Trackingを解説したいと思います。更新系の処理ですね。EF4ではPOCOのChange Trackingもちゃんとサポートする予定です。

EF4 POCOには2つの実装方法が存在します。それぞれについて解説していきましょう。

1 .Snapshot based Change Tracking without Proxies

名前からご想像できる通り、更新前後のスナップショットを保持するシンプルなChange Trackingのソリューションです。この手法は後半ご説明する方法と異なり純粋なPOCOを利用した実装が可能です。そのため、通常はこの手法を用いることをお勧めします。

ただし次のような制限があるので注意してください。

データを変更してもEntity FrameworkのObject State managerと同期しません。そのため以下のような現象が発生します。

 NorthwindContext db = new NorthwindContext();
 Product product = db.Products.Where(p => p.ProductID == 10).Single();
 ObjectStateEntry s =  db.ObjectStateManager.GetObjectStateEntry(product);
 Console.WriteLine(s.State);    // Unchanged
 product.ProductName = "ikura";
 Console.WriteLine(s.State);   // Still Unchanged
 db.SaveChanges();      

この例で、Productは実行時にも純粋なPOCOになっています。

 

しかし、エンティティへの変更は自動的にObjectStateManagerへ通知されません。なぜならPOCOとEntity Frameworkの間に自動通知機能が存在しないためです。そのため、エンティティのプロパティを変更してもステートはUnchangedのままになっています。

次のようにDetectChangesメソッドを用いることでEntity Framework側に変更情報を反映させることが可能です。(通常はSaveChagesメソッドを呼び出せば、内部的にDetectChangesがキックされるようなので、単純な更新では明示的に呼び出す必要はないでしょう。)

 NorthwindContext db = new NorthwindContext();
 Product product = db.Products.Where(p => p.ProductID == 10).Single();
 ObjectStateEntry s =  db.ObjectStateManager.GetObjectStateEntry(product);
 Console.WriteLine(s.State);     // Unchanged
 product.ProductName = "ikura";
 db.DetectChanges();
 Console.WriteLine(s.State);     // Modified
 db.SaveChanges();

DetectChangesを呼び出すと、変更前後のエンティティが全て比較されることになるので注意が必要です。どのエンティティが変更されたのかEntity Framework自身が分からないためです。そのため大量データの更新処理ではパフォーマンスが落ちてしまうケースがあると考えられます。どうしてもパフォーマンスが気になるようであれば、次に解説する**Notification base Change Tracking with Proxies**を利用することになります。

2. Notification base Change Tracking with Proxies

Proxy(具体的にはSystem.Data.Entity.DynamicProxies)を用いることで、POCOに不足している機能を補完するものです。これによりPOCOとEntity Frameworkの間に自動通知機能が追加されます。ただし、実際にはこの方法は純粋なPOCOの実装ではありません。実行時にエンティティは定義したPOCOクラスのサブクラスになります。

下図ではエンティティがSystem.Data.Entity.DynamicProxies.NorthwindModel.Productに変更されています。

 

そのような理由で、この手法を用いる場合、POCOのプロパティにはオーバーライドできるようvirtualキーワードを指定する必要があります。

     public class Product
     {
         public virtual int ProductID { get; set; }
         public virtual string ProductName { get; set; }
         public virtual int SupplierID { get; set; }
         public virtual string QuantityPerUnit { get; set; }
         public virtual decimal UnitPrice { get; set; }
         public virtual Int16 UnitsInStock { get; set; }
         public virtual Int16 UnitsOnOrder { get; set; }
         public virtual Int16 ReorderLevel { get; set; }
         public virtual bool Discontinued { get; set; }
         public virtual Category Category { get; set; }
     }

この手法では、次のようにProxyによってTrackされるエンティティはEntity FrameworkのObject State managerと同期します。(DetectChangesメソッドを呼び出す必要はありませんし、SaveChangesメソッドを呼び出しても内部的に呼び出されることはありません)

 NorthwindContext db = new NorthwindContext();
 Product product = db.Products.Where(p => p.ProductID == 10).Single();
 ObjectStateEntry s =  db.ObjectStateManager.GetObjectStateEntry(product);
 Console.WriteLine(s.State);  // Unchanged
 product.ProductName = "ikura";
 Console.WriteLine(s.State);   // Modified

この手法は、純粋なPOCOと言えないのでお勧めしませんが、先に解説したように変更時に無駄な比較をスキップするため、パフォーマンスという観点では優れています。基本的には**Snapshot based Change Tracking without Proxies**を利用して頂いて、どうしてもうまくいかないならばこの方法を利用して頂くとよいでしょう。

以上で一旦、POCOの解説は終了ですが、今後も最新情報はUpdateしていく予定です。