web-dev-qa-db-ja.com

C#Linq:複数の.Where()を* OR *句で組み合わせる

私は現在の問題について多くを検索してきましたが、その問題を解決するための本当の答えを見つけることができませんでした。

次のSQLを生成するLINQクエリを作成しようとしています。

_SELECT * FROM TABLE WHERE (Field1 = X, Field2 = Y ... ) or (Field3 = Z)
_

通常の状況では、私はこれを行うでしょう:

_Object.Where(c => (c.Field1 == X && c.Field2 == Y) || (c.Field3 == Z))
_

クエリは複数の.Where()呼び出しを使用して作成されているため、この方法は使用できません。

例があります:

_// This is a short example, the real world situation has 20 fields to check and they are all connected with an AND.
if (model.Field1.HasValue) 
{
    Query = Query.Where(c => c.Field1 == X)
}

if (model.Field2.HasValue) 
{
    Query = Query.Where(c => c.Field2 == X)
}

[...] like 20 more of these .Where() calls.
_

それが私にとって複雑になるのです。これらすべての.Where()呼び出しは、ANDに接続されたLinqクエリを構築していますが、これは問題ありません。

Parentheseで実行し、APIを使用して単純なORを追加するにはどうすればよいですか?

述語をいくつかの変数に保存して、次のようなものを作成する方法はありますか?

_Query = Query.Where(c => previousPredicates || c.Field3 == X)
_

またはその問題を解決する方法?

その特定の問題には適切な解決策があるはずだと思います。それを必要とするのは私だけではありませんが、それをどのように達成するかはまったくわかりません。

追伸:複数の.Where()呼び出しを実際に削除することはできず、直接SQLを作成することもできません。

[〜#〜] edit [〜#〜] StackOverflowは、なぜ私の質問が他の質問と異なるのかを私に述べて欲しいと思っています。まあ、それはParenthesesについてです。すべての.Where()を単一のOR句で接続したくないので、ANDのままにして別のORを追加したいすべてのANDクエリが括弧で囲まれている間の句。

15
thelostcode

すべてのレコードをフェッチしてメモリでクエリを実行するのではなく、プログラムでクエリを作成してSQLサーバーで実行する場合は、Expressionクラスで静的メソッドのセットを使用し、それらを使用してクエリを作成する必要があります。 。あなたの例では:

public class Query // this will contain your 20 fields you want to check against
{
    public int? Field1; public int? Field2; public int? Field3; public int Field4;
}

public class QueriedObject // this is the object representing the database table you're querying
{
    public int QueriedField;
}

public class Program
{
    public static void Main()
    {
        var queryable = new List<QueriedObject>().AsQueryable();
        var query = new Query { Field2 = 1, Field3 = 4, Field4 = 2 };

        // this represents the argument to your lambda expression
        var parameter = Expression.Parameter(typeof(QueriedObject), "qo");

        // this is the "qo.QueriedField" part of the resulting expression - we'll use it several times later
        var memberAccess = Expression.Field(parameter, "QueriedField");

        // start with a 1 == 1 comparison for easier building - 
        // you can just add further &&s to it without checking if it's the first in the chain
        var expr = Expression.Equal(Expression.Constant(1), Expression.Constant(1));

        // doesn't trigger, so you still have 1 == 1
        if (query.Field1.HasValue)
        {
            expr = Expression.AndAlso(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field1.Value)));
        }
        // 1 == 1 && qo.QueriedField == 1
        if (query.Field2.HasValue)
        {
            expr = Expression.AndAlso(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field2.Value)));
        }
        // 1 == 1 && qo.QueriedField == 1 && qo.QueriedField == 4
        if (query.Field3.HasValue)
        {
            expr = Expression.AndAlso(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field3.Value)));
        }

        // (1 == 1 && qo.QueriedField == 1 && qo.QueriedField == 4) || qo.QueriedField == 2
        expr = Expression.OrElse(expr, Expression.Equal(memberAccess, Expression.Constant(query.Field4)));

        // now, we combine the lambda body with the parameter to create a lambda expression, which can be cast to Expression<Func<X, bool>>
        var lambda = (Expression<Func<QueriedObject, bool>>) Expression.Lambda(expr, parameter);

        // you can now do this, and the Where will be translated to an SQL query just as if you've written the expression manually
        var result = queryable.Where(lambda);       
    }
}
10

まず、2つのFunc<T,bool>述語を簡単に組み合わせるためのヘルパー拡張メソッドを作成します。

 public static Func<T, bool> And<T>(this Func<T, bool> left, Func<T, bool> right) 
     => a => left(a) && right(a);

 public static Func<T, bool> Or<T>(this Func<T, bool> left, Func<T, bool> right)
     => a => left(a) || right(a);

次に、それらを使用して述語をチェーンできます。

var list = Enumerable.Range(1, 100);

Func<int, bool> predicate = v => true; // start with true since we chain ANDs first

predicate = predicate.And(v => v % 2 == 0); // numbers dividable by 2
predicate = predicate.And(v => v % 3 == 0); // numbers dividable by 3
predicate = predicate.Or(v => v % 31 == 0); // numbers dividable by 31

var result = list.Where(predicate);

foreach (var i in result)
    Console.WriteLine(i);

出力:

6
12
18
24
30
31
36
42
48
54
60
62
66
72
78
84
90
93
96
4
Adrian

Expressionを使用すると、次のような1つのステップで作成できます。

Expression<Func<Model, bool>> exp = (model => 
                                    ((model.Field1.HasValue && c.Field1 == X) &&
                                    (model.Field2.HasValue && c.Field2 == X)) ||
                                     model.Field3 == X
                                    )

述語を定義すると、クエリで簡単に使用できます。

var result = Query.AsQueryable().Where(exp)

この要旨のコードを確認してください: my Gist url

UPDATE 1:式を作成する手順を使用する必要がある場合は、次のように使用できます。

Expression<Func<Model, bool>> exp = c => true;
if (model.Field1.HasValue) 
{
    var prefix = exp.Compile();
    exp = c => prefix(c) && c.Field1 == X;
}

if (model.Field2.HasValue) 
{
    var prefix = exp.Compile();
    exp = c => prefix(c) && c.Field2 == X;
}

[...] like 20 more of these .Where() calls.
0

あなたはlinqについて独自の答えを持っています。

Dynamic.linq を使用した別のアプローチを紹介しましょう

// You could build a Where string that can be converted to linq.
// and do if sats and append your where sats string. as the example below
var query = "c => (c.Field1 == \" a \" && c.Field2 == Y) || (c.Field3 == \" b \")";
var indicator = query.Split('.').First(); // the indicator eg c
   // assume TABLE is the name of the class
var p = Expression.Parameter(typeof(TABLE), indicator);
var e = DynamicExpression.ParseLambda(new[] { p }, null, query);

// and simple execute the expression 
var items = Object.Where(e);
0
Alen.Toma