C# linqで動的なWhere句を生成する方法

こんにちは!トミセンです。

C#で便利なlinqですが、

「lingでWhere句って動的に作れないの?」

情報が少なくて思ったより手間取ってしまうこともありますよね。

今回はそんなお悩みを解決します。
ずばり!「C# linqで動的なWhere句を生成する方法」についてご紹介します。

目次

C# linqで動的なWhere句を生成する方法

拡張メソッドを使って、動的なWhere句を生成していきます。

ソースコード

public static class EnumerableExtensions
{
    /// <summary>
    /// 動的Where句作成(複数)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="source"></param>
    /// <param name="expressions">カラム名, 値, 型, "And"・"Or"</param>
    /// <returns></returns>
    public static IEnumerable<T> DynamicWhere<T>(this IEnumerable<T> source, List<Tuple<string, object, Type, LinqExpression>> expressions)
    {
        var queryableSource = source.AsQueryable();

        // 条件作成
        var lambda = GetPredicate<T>(expressions);

        return queryableSource.Where(lambda);
    }

    /// <summary>
    /// 動的Where句条件作成
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="expressions"></param>
    /// <returns></returns>
    private static Func<T, bool> GetPredicate<T>(List<Tuple<string, object, Type, LinqExpression>> expressions)
    {
        // パラメータの定義する:x
        ParameterExpression param = Expression.Parameter(typeof(T), "x");

        // 全体のbody
        BinaryExpression body = null;

        int index = 0;
        foreach (var exp in expressions)
        {
            // x => x.Id == id
            // Idのbodyの左を定義する:x.Id
            MemberExpression left = Expression.Property(param, exp.Item1);

            // Idのbodyの右を定義する:id
            ConstantExpression right = Expression.Constant(exp.Item2, exp.Item3);

            // Idのbodyを定義する:x.Id == id
            BinaryExpression bodyDetails = Expression.Equal(left, right);

            if (exp.Item4 == LinqExpression.And)
            {
                body = (index == 0) ? bodyDetails : Expression.And(body, bodyDetails);
            }
            else
            {
                body = (index == 0) ? bodyDetails : Expression.Or(body, bodyDetails);
            }
            index++;
        }

        // 式ツリーを(x => x.Id == id || x => x.Name == name)を組み立て、
        // 実行コードにコンパイルする
        return Expression.Lambda<Func<T, bool>>(body, param).Compile();
    }
}

/// <summary>
/// 条件式
/// </summary>
public enum LinqExpression : int
{
    And = 1,
    Or = 2,
}		

使い方

それでは、使い方です。

条件を3回追加して絞っていきます。

	public class Test
{
    public void Main()
    {
        // テストデータ作成
        var models = CreateModels();

        Console.WriteLine($"元データ");
        // 結果出力
        WriteConsole(models);
		
        // 【実行結果】
        // 元データ
        // Code:1111  PatternNo:10  Kind:333333
        // Code:1111  PatternNo:10  Kind:333333
        // Code:1111  PatternNo:10  Kind:444444
        // Code:1111  PatternNo:10  Kind:444444
        // Code:1111  PatternNo:66  Kind:444444
        // Code:1111  PatternNo:66  Kind:444444
        // Code:3333  PatternNo:10  Kind:444444
        // Code:3333  PatternNo:10  Kind:333333
		

        var exp = new List<Tuple<string, object, Type, LinqExpression>>();

        Console.WriteLine($"");
        Console.WriteLine($"① 追加条件:Code == 1111");
        // 動的な検索条件作成
        exp.Add(CreateWhere(nameof(Member.Code), 1111, typeof(int)));
        // 動的な検索
        var models2 = models.DynamicWhere(exp).ToList();
        // 結果出力
        WriteConsole(models2);
		
        // 【実行結果】
        // ① 追加条件:Code == 1111
        // Code:1111  PatternNo:10  Kind:333333
        // Code:1111  PatternNo:10  Kind:333333
        // Code:1111  PatternNo:10  Kind:444444
        // Code:1111  PatternNo:10  Kind:444444
        // Code:1111  PatternNo:66  Kind:444444
        // Code:1111  PatternNo:66  Kind:444444


        Console.WriteLine($"");
        Console.WriteLine($"② 追加条件:PatternNo == 10");
        // 動的な検索条件作成
        exp.Add(CreateWhere(nameof(Member.PatternNo), 10, typeof(int)));
        // 動的な検索
        models2 = models.DynamicWhere(exp).ToList();
        // 結果出力
        WriteConsole(models2);
		
        // 【実行結果】
        // ② 追加条件:PatternNo == 10
        // Code:1111  PatternNo:10  Kind:333333
        // Code:1111  PatternNo:10  Kind:333333
        // Code:1111  PatternNo:10  Kind:444444
        // Code:1111  PatternNo:10  Kind:444444


        Console.WriteLine($"");
        Console.WriteLine($"③ 追加条件:Kind == 333333");
        // 動的な検索条件作成
        exp.Add(CreateWhere(nameof(Member.Kind), 333333L, typeof(long)));
        // 動的な検索
        models2 = models.DynamicWhere(exp).ToList();
        // 結果出力
        WriteConsole(models2);
		
        // 【実行結果】
        // ③ 追加条件:Kind == 333333
        // Code:1111  PatternNo:10  Kind:333333
        // Code:1111  PatternNo:10  Kind:333333
    }

    /// <summary>
    /// 検索条件作成
    /// </summary>
    /// <param name="columnName">項目名</param>
    /// <param name="value">値</param>
    /// <param name="type">クラス型</param>
    /// <returns></returns>
    private Tuple<string, object, Type, LinqExpression> CreateWhere(string columnName, object value, Type type)
    {
        return new Tuple<string, object, Type, LinqExpression>(columnName, value, type, LinqExpression.And);
    }

    // ======================
    //  テスト用
    // ======================

    /// <summary>
    /// テストデータ作成
    /// </summary>
    /// <returns></returns>
    private List<Member> CreateModels()
    {
        var models = new List<Member>();

        models.Add(Create(1111, 10, 333333L));
        models.Add(Create(1111, 10, 333333L));
        models.Add(Create(1111, 10, 444444L));
        models.Add(Create(1111, 10, 444444L));
        models.Add(Create(1111, 66, 444444L));
        models.Add(Create(1111, 66, 444444L));
        models.Add(Create(3333, 10, 444444L));
        models.Add(Create(3333, 10, 333333L));

        return models;
    }

    /// <summary>
    /// Memberクラス作成
    /// </summary>
    /// <param name="code"></param>
    /// <param name="patternNo"></param>
    /// <param name="kind"></param>
    /// <returns></returns>
    private Member Create(int code, int patternNo, long kind)
    {
        return new Member
        {
            Code = code,
            PatternNo = patternNo,
            Kind = kind,
        };
    }

    /// <summary>
    /// 結果出力
    /// </summary>
    /// <param name="models"></param>
    private void WriteConsole(List<Member> models)
    {
        foreach (var model in models)
        {
            Console.WriteLine($"  Code:{model.Code}  PatternNo:{model.PatternNo}  Kind:{model.Kind}");
        }
    }
}

public class Member
{
    ///<summary>コード</summary>
    public int Code { get; set; }

    ///<summary>パターン</summary>
    public int PatternNo { get; set; }

    ///<summary>種別</summary>
    public long Kind { get; set; }
}	

まとめ

画面項目などによって、条件が変わる場合など使えるケースもあると思います。

それでは、また。

参考サイト

【C#】LINQ WHERE 動的|demicadeのブログ

よかったらシェアしてね!
目次
閉じる