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 ListwkConstList = 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
上記のソースで発行されるクエリは↓の条件だった。
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に突っ込めば動く。