くりーむわーかー

プログラムとか。作ってて ・試しててハマった事など。誰かのお役に立てば幸いかと。 その他、いろいろエトセトラ。。。

ラムダ式

C# ラムダ式を動的に作る

DBとのやり取りをEF使って、Linqとかラムダ式でやってる場合、 固定的な奴はいいんだけど、やっぱ動的な条件付けが必要になることがある。

SQLを直で書いてもいいと思うんだけど、せっかくなのでラムダ式も 汎用的にやりたい。いきなり書くとわけわかんないコードになるので、 汎用的にやるために必要な部分をテスト的に書いたものが↓。

いちをこんな感じのある程度複雑なクエリを投げる想定

select * from 台帳
where 
(
	(
	顧客名 = 'お客さん1'
	OR
	顧客名 = 'お客さん2'
	)
	AND 
	(日付 >= '2016-10-01')
)
OR
(
	(
		業務 in ('業務1','業務2','業務3')
		AND
		媒体 = 'メール'
	)
	AND 
	(日付 >= '2016-10-01')
)

で、ソース。

using System.Linq.Expressions;
using System.Reflection;

public List<HogeTable> wkTest()
{
    var predList1 = new List<Expression>();
    var predList2 = new List<Expression>();
    var predList3 = new List<Expression>();
    var param = Expression.Parameter(typeof(HogeTable), "p");

    var left1 = Expression.PropertyOrField(param, "顧客名");
    var left2 = Expression.PropertyOrField(param, "業務");
    var left3 = Expression.PropertyOrField(param, "媒体");
    var left4 = Expression.PropertyOrField(param, "日付");

    //in句用のList
    List wkConstList = new List<string>();
    wkConstList.Add("業務1");
    wkConstList.Add("業務2");
    wkConstList.Add("業務3");

    //条件用の値準備
    var right1 = Expression.Constant("お客さん1");
    var right2 = Expression.Constant("お客さん2");
    var right3 = Expression.Constant("メール");
    var right4 = Expression.Constant("2016-10-01");
    var right5 = Expression.Constant(wkConstList, typeof(List<string>));

    predList1.Add(Expression.Equal(left1, right1));
    predList1.Add(Expression.Equal(left1, right2));
    
    //IN句の代用
    MethodInfo Contains = typeof(List<string>).GetMethod("Contains");
    predList2.Add(Expression.Call(right5, Contains, left2));
    predList2.Add(Expression.Equal(left3, right3));
    //文字列の大小比較
    predList3.Add(Expression.GreaterThanOrEqual(Expression.Call(typeof(string), "Compare", null,left4, right4 ), Expression.Constant(0)));

    var tmpbody1 = predList1.Aggregate((l, r) => Expression.MakeBinary(ExpressionType.OrElse, l, r));
    var tmpbody2 = predList2.Aggregate((l, r) => Expression.MakeBinary(ExpressionType.AndAlso, l, r));
    var tmpbody3 = predList3.Aggregate((l, r) => Expression.MakeBinary(ExpressionType.AndAlso, l, r));

    var body1 = Expression.MakeBinary(ExpressionType.AndAlso, tmpbody2, tmpbody3);
    var body2 = Expression.MakeBinary(ExpressionType.AndAlso, tmpbody1, tmpbody3);
    var body3 = Expression.MakeBinary(ExpressionType.OrElse, body2, body1);

    IEnumerable<HogeTable> wkResult;
    wkResult = HogeTables.AsQueryable().Where(Expression.Lambda<Func<HogeTable, bool>>(body3, param));

    return wkResult.ToList();
}

いちを、全部、Expressionで動的に定義してるので、あとは一工夫してあげれば出来上がりという寸法です。

個人的に条件文のネストといいますか、()の深い構造をどう作るのかな~っていうのを試したかった。 あと、Expression使う場合に文字列の大小比較と、IN句に対応する「List.Contains(x.列名)」的な書き方がいけるのか確認しておきたかったのでそれも。。。

上記のソースで発行されるクエリは↓の条件だった。

WHERE 
(
  ([Extent1].[顧客名] IN (N'お客さん1',N'お客さん2')) 
  AND 
  ([Extent1].[日付] >= N'2016-10-01')
) 
OR 
(
  ([Extent1].[業務] IN (N'業務1', N'業務2', N'業務3')) 
  AND
  ([Extent1].[業務] IS NOT NULL) 
  AND
  (N'メール' = [Extent1].[媒体]) 
  AND
  ([Extent1].[日付] >= N'2016-10-01')
)

おおむね、想定通りのクエリ。文字列の大小比較とかいけるか怪しい気がしたけどちゃんと行けてるの。あとOrで繋げた部分も勝手に判断してin句に変えてくれてる。賢い。ただ、「is not null」がじゃっかん気になるところ。。。

で、最初はBlock使わないと条件文のネストしてくれないかと思ってたんだけど、順番つけて普通にAndAlsoとかOrElseで繋げれば勝手にやってくれるっぽ。てか、DB宛てじゃないモデルの場合はBlock使えるんだけど、テーブルが裏にいるEFのモデルだとBlock使わせてくれないのかしら。何かエラーになる。あまし調べてない。。。

あー、Linqの書き方の場合でも上記のExpressionそのままWhereに突っ込めば動く。

C#のラムダ式まとめ

あんま慣れてないのでいっつも忘れるのでメモ。

public void test()
{
    //delegate使って匿名メソッドを定義
    Func<int, int> x = delegate (int n) { return n * 10; };
    Console.WriteLine(x(1));

    //↑をラムダ式で
    x = (n) => { return n * 20; };
    Console.WriteLine(x(1));

    //↑をラムダ式でさらに簡素に
    x = (n => n * 30);
    Console.WriteLine(x(1));

    //中身の匿名関数をラムダ式でその場で定義して var に入れる
    var y = new Func<int, int>(n => { return n * 40; });
    Console.WriteLine(y(1));

    //引数が複数の場合のdelegate
    Func<int, int, int> xx = delegate (int n, int nn) { return n * nn; };
    Console.WriteLine(xx(1,10));

    //引数が複数の場合のラムダ式
    xx = (n, nn) => { return n * nn; };
    Console.WriteLine(xx(1, 10));

    //中身の匿名関数をラムダ式でその場で定義して var に入れる 引数複数版
    var yy = new Func<int, int, int>((n,nn) => { return n * nn; });
    Console.WriteLine(yy(1, 10));

    //引数無しの場合のdelegate
    Func<int> xxx = delegate () { return 20; };
    Console.WriteLine(xxx());

    //引数が複数の場合のラムダ式
    xxx = () => { return 20; };
    Console.WriteLine(xxx());

    //中身の匿名関数をラムダ式でその場で定義して var に入れる 引数複数版
    var yyy = new Func<int>(() => { return 20; });
    Console.WriteLine(yyy());


    //Actionの場合 引数1個
    Action<int> a = delegate (int n) { Console.WriteLine(n); };
    a(30);
    a = (n) => Console.WriteLine(n);
    a(30);
    a = n => Console.WriteLine(n);
    a(30);
    //var b = new Action<int>(n => Console.WriteLine(n));
    var b = new Action<int>( (n) => { Console.WriteLine(n); });
    b(30);


    //Actionの場合 引数2個
    Action<int,int> aa = delegate (int n,int nn) { Console.WriteLine(n*nn); };
    aa(4, 10);
    aa = (n, nn) => { Console.WriteLine(n * nn); };
    aa(4, 10);
    var bb = new Action<int, int>((n, nn) => { Console.WriteLine(n * nn); });
    bb(4, 10);

    //Actionの場合 引数無し
    Action aaa = delegate () { Console.WriteLine(0); };
    aaa();
    aaa = () => { Console.WriteLine(0); };
    aaa();
    aaa = () => Console.WriteLine(0);
    aaa();
    var bbb = new Action( () => { Console.WriteLine(0); } );
    bbb();
}

C#のラムダ式で複数項目のGroupByしてCountする

タイトル通り。ちなみにいまだに下の感じのメソッドチェーンがラムダ式と言っていいのかわかってなかったりする。。。今度ちゃんとやろ。

foreach (var tmp in wkListObj.Where(n => n.DateVal == wkDate)	
                             .GroupBy(n=> new { n.DateVal,n.HogeVal,n.FugaVal})
                             .Select(g=> new { DateVal = g.Key.DateVal,HogeVal = g.Key.HogeVal,FugaVal = g.Key.FugaVal,件数 = g.Count()})
)
{
    Console.WriteLine("{0}:{1}","DateVal",tmp.DateVal);
    Console.WriteLine("{0}:{1}","HogeVal",tmp.HogeVal);
    Console.WriteLine("{0}:{1}","FugaVal",tmp.FugaVal);
    Console.WriteLine("{0}:{1}","件数",tmp.件数);
}
問合せ