質問: SqlUpdateQueryCreator.create(hoge); ``` みたいに呼んだら、sqlとして`update HOGE set B=b, C=c`を返すような 仕組みを作りたいです。この場合はAプロパティを何らかの形で 無視するようにしたいです。 なお、Entityクラスを関数に渡して、Entityのすべてのプロパティを InsertするSQL文を動的に作成するのはリフレクションを使ってできました。 お知恵を拝借できれば幸いです。
回答: `System.Linq.Expressions.MemberInitExpression`の式木を使用すればよいと思います。例えば以下のような引数のメソッドと呼び出し方を実現できます。 ``` void Update(Expression> expression); Update(() => new Entity() { B = b, C = c }); ``` メソッドの内部では`expression.Body`を`MemberInitExpression`にキャストして`Bindings`プロパティを参照すると各初期化子の式にアクセスすることが出来ます。 それから各`MemberBinding`の値を評価するのですが、これは 1. `ConstantExpression`にキャストする 2. `Expression.Lambda`でラムダ式の式木を作成し、さらにデリゲートにコンパイルする。 3. `ExpressionVisitor`を用いて複合的に処理する というような方針が考えられます。以下の例ではすべての列の値を`object[]`で取り出しています。 ``` void Update(Expression> expression) { // SQLバッファ var sb = new StringBuilder(); // 値の式のリスト var values = new List(); // UPDATE文の先頭部分 sb.Append("UPDATE [Entities] SET "); foreach (var b in ((MemberInitExpression)expression.Body).Bindings) { // b.Memberから列名を取得する sb.Append('[').Append(b.Member.Name).Append("] = "); // ((MemberAssignment)b).Expressionから値を取得する sb.Append("@p").Append(values.Count); values.Add(((MemberAssignment)b).Expression); sb.Append(", "); } // 余計な", "を削除する sb.Length -= 2; // TODO:Whereを生成する // 生成したSQLの表示 Console.WriteLine("SQL:"); Console.Write(" "); Console.WriteLine(sb); // valuesの値をobject[]として取得するメソッドを作成する var valueGetter = Expression.Lambda>( Expression.NewArrayInit( typeof(object), values.Select(v => Expression.Convert(v, typeof(object))) ) ).Compile(); // valuesの表示 Console.WriteLine("Values:"); var actual = valueGetter(); for (int i = 0; i < actual.Length; i++) { Console.WriteLine($" @p{i}: {actual[i]}"); } } ``` なお汎用的に`WHERE`句を作成する場合は引数に`Expression> predicate`を追加しますが、これには必ず`ParameterExpression`が含まれるため、`ExpressionVisitor`を継承して 1. `ParameterExpression`への`MemberAccessExpression`はSQLフィールドに変換する。 2. 1を含まない式木は`Expression.Lambda`でパラメーター化する 3. 1と2を含む式木はSQL上の演算に置き換える。 というような処理を実装する必要があります。