30100


Entityクラスのうち、指定のプロパティのみをupdateする  
SQL文を動的に作りたいですが、何か良い方法はありませんでしょうか？  
where文は外から指定する形でwhereの手前までを動的に作成したいです。  
Entityの各プロパティに対して、UPDATE文の対象とするような  
フラグを持たせればできますが、既存のEntityのプロパティは  
変更せずにそのまま使いたいです。  
カスタムAttribute等を付加することも検討しましたが、Attributeは  
インスタンス生成後に変更はできないようなので、難しそうです。。  
例：HogeクラスにA,B,Cプロティがあるとして、

[code]

    Hoge hoge = new Hoge();
    hoge.setB(b);
    hoge.setC(c);
    string sql = SqlUpdateQueryCreator.create(hoge);
    
[/code]

みたいに呼んだら、sqlとして`update HOGE set B=b, C=c`を返すような  
仕組みを作りたいです。この場合はAプロパティを何らかの形で  
無視するようにしたいです。  
なお、Entityクラスを関数に渡して、Entityのすべてのプロパティを  
InsertするSQL文を動的に作成するのはリフレクションを使ってできました。  
お知恵を拝借できれば幸いです。


CC BY-SA 3.0
2016-11-04T10:01:43.203
30097
2016-11-10T08:41:54.243
2016-11-10T08:41:54.243
9228
9228
question
c#
C#で動的にSQL文を作成できないでしょうか？(Update文)


`System.Linq.Expressions.MemberInitExpression`の式木を使用すればよいと思います。例えば以下のような引数のメソッドと呼び出し方を実現できます。

[code]

    void Update(Expression<Func<Entity>> expression);
    
    Update(() => new Entity()
    {
        B = b,
        C = c
    });
    
[/code]

メソッドの内部では`expression.Body`を`MemberInitExpression`にキャストして`Bindings`プロパティを参照すると各初期化子の式にアクセスすることが出来ます。  
それから各`MemberBinding`の値を評価するのですが、これは

  1. `ConstantExpression`にキャストする
  2. `Expression.Lambda`でラムダ式の式木を作成し、さらにデリゲートにコンパイルする。
  3. `ExpressionVisitor`を用いて複合的に処理する

というような方針が考えられます。以下の例ではすべての列の値を`object[]`で取り出しています。

[code]

    void Update(Expression<Func<Entity>> expression)
    {
        // SQLバッファ
        var sb = new StringBuilder();
    
        // 値の式のリスト
        var values = new List<Expression>();
    
        // 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<Func<object[]>>(
                                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]}");
        }
    }
    
[/code]

なお汎用的に`WHERE`句を作成する場合は引数に`Expression<Func<Entity, bool>>
predicate`を追加しますが、これには必ず`ParameterExpression`が含まれるため、`ExpressionVisitor`を継承して

  1. `ParameterExpression`への`MemberAccessExpression`はSQLフィールドに変換する。
  2. 1を含まない式木は`Expression.Lambda`でパラメーター化する
  3. 1と2を含む式木はSQL上の演算に置き換える。

というような処理を実装する必要があります。


CC BY-SA 3.0
2016-11-04T10:29:47.743
30100
2016-11-07T03:15:25.990
2016-11-07T03:15:25.990
5750
5750
30097
answer