DelegateDecompilerでEntityFrameworkをぱわわっぷ(入門編)
はじめに
こんにちは、みなさんEntityFrameworkでドメインモデルつくってますか?(EFじゃできねーよ!という声が聞こえてきます…)
今日はEFプログラミングでドメインモデルづくりを促進するライブラリDelegateDecompilerを紹介します。
このライブラリはEntityFramework専用というわけではないですが、 個人的に便利なユースケースとしてEntityFrameworkプログラミングを中心に解説を行います。
概要
DelegateDecompilerは、Delegate のIL(中間言語)を解析して、 式木にDecompileしてくれるライブラリです。
例えば、下記のような Expression(式木) -> Delegate がCompileだとすれば
Expression<Func<int, bool>> expr = (int i) => i == 5; Func<int, bool> compiled = expr.Compile();
逆操作である、Delegate -> Expression(式木) は Decompile(逆コンパイル)ですね!
Func<int, bool> del = (int i) => i == 5; Expression<Func<int, bool>> decompiled = del.Decompile();
もちろんすべてのILを式木に変換してくれるわけじゃありません。 だけど、Linq to Entitiesで使うとしたらSQLに変換できるレベルのILは割とがんばって変換してくれます。
導入方法は、Nugetに登録されているのでDelegateDecompilerで検索すればヒットします。
EntityFrameworkの不便さ
DelegateDecompilerを使う前に、EntityFrameworkの不便さを味わってみましょう。
下記のようなエンティティクラスがあるとします。(簡単のため細かいところは端折っています)
public class Person { // FirstName と LastName はデータベースのスキーマとマッピングされています。 public string FirstName { get; set; } public string LastName{ get; set; } // FullName はデータベースのスキーマではない public string FullName { get { return FirstName + " " + LastName; } } }
※ Model/Database-firstの場合はparshal classを使ってください。
Person のリポジトリ(IQueryable)に対してFullNameを検索するにはこんなクエリを書きたいでしょう。
// 実行時にNotSupportedException var result = people.Where(p => p.FullName == "Taro Yamada").Single();
※ Singleのオーバーロードを使わないのはDelegateDecompilerを使うための布石
残念ながらこの呼び出しはLinq to Entitiesでは実行時エラーです。 プロパティの呼び出しという式木は、SQLに変換できないからです。
正しく動作させるためには、下記のように修正します。
var result = people.Where(p => (p.FirstName + " " + p.LastName) == "Taro Yamada").Single();
このように、クエリの式木で表現できるのはデータベースのスキーマと直接マッピングできるものだけに限定されてしまいます。
おかげでせっかく定義したFullNameが台無しですね。
DelegateDecompilerを使う
DelegateDecompiler で上のコードを改善するのはすごく簡単!
Decompileする必要のあるメソッド、もしくはプロパティにComputed属性をつけて、
[Computed] public string FullName { get { return FirstName + " " + LastName; } }
クエリに対して Decompile メソッドを呼ぶだけです。(クエリがToList,SingleなどでSQLが発行される前に)
var result = people.Where(p => p.FullName == "Taro Yamada").Decompile().Single();
結果的には、FullNameプロパティ呼び出しは、その実装であるFirstName + " " + LastName
が式木に変換されます。
これは明らかにSQLに変換可能ですね! めでたしめでたし。
最後に
とても便利なDelegateDecompilerですが、使用する上で気を付けなければいけない点があります。
例えば…
- 何度も言いますが、すべてのメソッド、プロパティを式木に変換できるわけではありません。 もっともEntityFrameworkと併せて使う場合は、式木に変換できてかつ、式木からSQLに変換できる必要があります。
- 式木に変換できなかった場合、Stackが空です のような一見不可解な例外が発生します。
- クエリが実行されてSQLが発行されたあとにDecompileしても何の意味もないので、SQLが実際に発行されるタイミングを十分に意識する必要があります。(Linq to Entitiesだけに限ってもそうだけどね…)
ラムダ式を含むメソッド、プロパティは今日時点のDelegateDecompilerでは式木に変換できません。 現在、対応版をpull requestをしているところです。私の github から直接 clone してくれば問題ありません。-> v0.8.0から対応
今回は、EntityFrameworkに絞ってDelegateDecompilerを紹介しましたが、それ以外の可能性も無限大です!