Entity Frameworkを使用する上で、検索条件を動的に組み立てる方法メモです。
Entity Frameworkは通常、Entity.Fieldに対して必要な条件を指定します。
そうすることでSQLを直に書かなくても、裏で勝手にSQLを生成して実行してくれます。
しかし、Where句を動的に組み立てるにはちょっとコツが要ります。
まず、ANDとORは別に考える必要があります。
ANDは、条件の数だけcontext.対象テーブルにWhereメソッドを実行します。
取得データを徐々に絞っていくと言えばイメージしやすいでしょうか。
対してORは少し面倒です。
Expressionの拡張メソッドを実装します。
ExpressionCombiner.cs
public static class ExpressionCombiner
{
public static Expression<Func<T, bool>> OrElse<T>(params Expression<Func<T, bool>>[] expressions)
{
return OrElse(expressions.AsEnumerable());
}
public static Expression<Func<T, bool>> OrElse<T>(this IEnumerable<Expression<Func<T, bool>>> expressions)
{
if (!expressions.Any())
{
throw new ArgumentException($"param:[{nameof(expressions)}] is empty", nameof(expressions));
}
var lambda = expressions.First();
var body = lambda.Body;
var parameter = lambda.Parameters[0];
foreach (var expression in expressions.Skip(1))
{
var visitor = new ParameterReplaceVisitor(expression.Parameters[0], parameter);
body = Expression.OrElse(body, visitor.Visit(expression.Body));
}
lambda = Expression.Lambda<Func<T, bool>>(body, parameter);
return lambda;
}
}
ParameterReplaceVisitor.cs
public class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression _originalParameter;
private readonly ParameterExpression _newParameter;
public ParameterReplaceVisitor(ParameterExpression originalParameter, ParameterExpression newParameter)
{
this._originalParameter = originalParameter;
this._newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (node == this._originalParameter)
{
return this._newParameter;
}
return base.VisitParameter(node);
}
}
あとはデータ抽出用に、AND用の条件リストと、OR用の条件リストを作成します。
private void CreateExpression(ref List<Expression<Func<ENTITY_NAME, bool>>> andEx,
ref List<Expression<Func<ENTITY_NAME, bool>>> orEx)
{
// AND条件のリストに追加
andEx.Add(x => x."数値フィールド" == 0);
andEx.Add(x => x."日付フィールド" >= "2018/01/01" &&
x."日付フィールド" <= "2018/12/31");
// OR条件のリストに追加
orEx.Add(x => x."文字列フィールド".Contains("hoge"));
orEx.Add(x => x."文字列フィールド".Contains("fuga"));
}
使い方はこんな感じで。
// 検索条件の生成
var andExList = new List<Expression<Func<ENTITY_NAME, bool>>>();
var orExList = new List<Expression<Func<ENTITY_NAME, bool>>>();
CreateExpression(ref andExList, ref orExList);
//コンテキスト生成
using (ExampleEntities context = new ExampleEntities())
{
var data = context.ENTITY_NAME.AsQueryable();
foreach (var andEx in andExList)
{
// ANDは単純にチェーンしてきます。
data = data.Where(andEx);
}
if (orExList.Count > 0)
{
// ORは先ほどの拡張メソッドを呼ぶ。
data = data.Where(ExpressionCombiner.OrElse(orExList));
}
}
// あとは取得したデータをどうにかする
ExpressionCombinerはラムダ式全般に使えます。
おしまい。