ADO.NET Entity Framework を用いたN階層システムの構築手法
今日は ADO.NET Entity Framework + N 階層システムについて考えてみたいと思います。
現バージョンの ADO.NET Enity Framework ではシリアライズ対象でない ObjectContext が変更履歴(CRUDステータス 、Old Value、FKチェックなどなど)を管理しているため、特に同時実行制御を考慮したN階層システムの構築が困難なケースがあります。
当然ながら変更履歴を管理するDTOなどを自身で作成すれば対応できますが、スキーマ構造が複雑化するにつれて相乗的に実装が困難になるでしょう。
その結果、WCFなどを使った更新を伴うデータサービスの構築を考えた場合、必ずしもDataSetより優れているとは言い難いのが現状だと思います。
この問題の解決策として登場するのが Microsoft ADO.NET Entity Framework Feature Community Technology Preview に含まれる Templates for Self-Tracking Entities (N-Tier support) になります。
これは 以前のPost でも少し紹介しました。おそらく乱暴すぎてよく理解できなかった方々がほとんどでしょう。(反省)
今回は実際にいじってみましょう。
尚、ADO.NET team のBlogでも紹介されているので、ちちらも参考にしてください。
※ US版サンプルコードダウンロード : EFFeatureCTP2_Walkthrough_STE.ZIP
環境
・Visual Studio 2010 Beta 2 (English) ※現状日本語版では動きません
・Microsoft ADO.NET Entity Framework Feature Community Technology Preview 2
・SQL Server 2005/2008
プロジェクトの構成
・MSDAL
Entity Frameworkを使ったデータアクセスレイヤーを定義
・MSDTO
DTO(Data Transfer Object)を定義
・MSWcfClient
サービスクライアントのコンソールアプリ
・MSWcfService
WCFサービス
開発スタート (※あくまでデモ用サンプルです)
1.MSDALの作成
今回はモデルファーストで作っていきましょう。employee と company という名前のエンティティを以下のように定義します。
後で楽観的同時実行制御の動作確認もしたいので、各プロパティの「Concurrency Mode」はすべて「Fixed」に設定しておきます。
モデルの定義が終わったら、コンテキストメニューから「Generate Database from Model」」でSQLスクリプトを作成して、実行します。
コードビハインドは不要なので以下のようにCustom Tool の Code Generator を削除しておきます。
ここまでは復習です。ご存じない方は以前のポストを参考にしてください。
次にデザイナ上のコンテキストメニューから「Add Code Generation Item…」を選択します。
すると テンプレートで「ADO.NET Self-Tracking Entities」が選択できます。
※現状、日本語版VSでは出てきません。
実行するとT4 Template 「XXX.Context.tt」 と 「XXX.Types.tt」 というファイルが追加されているのがわかります。中身をみてみると Object Context と エンティティ(今回は company と employee)が存在します。
今回、エンティティはMSDTOプロジェクトで管理したいとおもいます。そのため、一旦今あるエンティティを削除します。
手順は「XXX.Types.tt」のCustom Toolを以下のように削除します。また、自動的に「XXX.Types.tt」配下のエンティティは削除されないので手動で削除しておきます。
よやく MSDAL の作成完了です。
2.MSDTOの作成
下記のようにAdd Existing Item で先ほど作成した「XXX.Types.tt」を選択します。「Add As Link」で追加するのをお忘れなく。
すると、プロジェクトに company.cs、employee.cs、XXX.Types.cs が作成されているのがわかります。
これでMSDTOは完成です。
3.MSWcfService
作成したプロジェクト「MSDAL」、「MSDTO」への参照を追加しておきます。またWeb.configにデータベースへの接続文字列も追加しておきましょう。
実行コードは以下の通り。
サービスインタフェース
namespace MSWcfService { [ServiceContract] public interface IMSService { [OperationContract] company getCompany(int id); [OperationContract] bool updateCompany(company c); [OperationContract] employee getEmployee(int id); [OperationContract] bool updateEmployee(employee e); } } |
サービスの実装
ポイントは以下3点だと思います。
・LINQのincludeオプションを使ってcompanyに紐づくemployeeもまとめて取得している
・OptimisticConcurrencyExceptionで楽観的同時実行のエラーを受け取っている
・ApplyChanges メソッドのみで削除、追加、更新すべてに対応できる
namespace MSWcfService { public class MSService : IMSService { MSModelContainer db = new MSModelContainer(); public company getCompany(int id) { var result = db.companySet.Include("employee").Where(c => c.companyId == id).First(); return result; } public bool updateCompany(company c) { try { db.companySet.ApplyChanges<company>(c); db.SaveChanges(); } catch (OptimisticConcurrencyException ex) { return false; } return true; } public employee getEmployee(int id) { var result = db.employeeSet.Where(e => e.employeeId == id).First(); return result; } public bool updateEmployee(employee e) { try { db.employeeSet.ApplyChanges<employee>(e); db.SaveChanges(); } catch (OptimisticConcurrencyException ex) { return false; } return true; } } |
サービス側の実装がものすごくシンプルになるのがご確認いただけるでしょう。
4.MSWcfClient
作成したプロジェクト「MSDTO」への参照を追加しておきます。
また「Add Service Reference」で先ほど作成した「MSWcfService」へのサービス参照を追加しておきましょう。以上で設定は完了。
あとはクライアントからいろいろ試すだけです。
ちゃんと楽観的同時実行制御も実装されてますし、関連のある複数エンティティをまとめて一括更新することもできます。
参考までにテストプログラムを載せておきます。
static void Main(string[] args) { using (var service = new MSWcfClient.ServiceReference1.MSServiceClient()) { { // companyデータの追加 company c = new company(); c.companyId = 1; c.companyName = "Microsoft"; c.address = "OST"; service.updateCompany(c); } { // company データの取得 company c = service.getCompany(1); // employee データ追加 employee e = new employee(); e.employeeId = 1; e.employeeName = "Daisuke Inoue"; e.company = c; service.updateEmployee(e); } { // company、employee データの取得 company c = service.getCompany(1); employee e = c.employee.First(); // employee データ更新 e.employeeName = "Akira Inoue"; service.updateCompany(c); } { // 全データ削除 company c = service.getCompany(1); employee e = c.employee.First(); e.MarkAsDeleted(); service.updateCompany(c); } } } |
クライアント側も非常にシンプルなコードになります。こいつはめちゃくちゃ便利です!!
リソース情報
・n 層アプリケーションで回避すべきアンチパターン https://msdn.microsoft.com/ja-jp/magazine/dd882522.aspx
**
・n 層アプリケーションのパターン https://msdn.microsoft.com/ja-jp/magazine/ee321569.aspx
・EF4 による n 層アプリケーションの構築 https://msdn.microsoft.com/ja-jp/magazine/ee335715.aspx