DelegateDecompilerでEntityFrameworkをぱわわっぷ(入門編)

はじめに

こんにちは、みなさんEntityFrameworkでドメインモデルつくってますか?(EFじゃできねーよ!という声が聞こえてきます…)

今日はEFプログラミングでドメインモデルづくりを促進するライブラリDelegateDecompilerを紹介します。

このライブラリはEntityFramework専用というわけではないですが、 個人的に便利なユースケースとしてEntityFrameworkプログラミングを中心に解説を行います。

概要

DelegateDecompilerは、DelegateIL中間言語)を解析して、 式木にDecompileしてくれるライブラリです。

例えば、下記のような Expression(式木) -> DelegateCompileだとすれば

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を紹介しましたが、それ以外の可能性も無限大です!