.net

【.net】Entity FrameworkでWhere句を動的に生成する方法

Entity Frameworkを使用する上で、検索条件を動的に組み立てる方法メモです。

Entity Frameworkは通常、Entity.Fieldに対して必要な条件を指定します。
そうすることでSQLを直に書かなくても、裏で勝手にSQLを生成して実行してくれます。
しかし、Where句を動的に組み立てるにはちょっとコツが要ります。


動的に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はラムダ式全般に使えます。
おしまい。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です