C # Linq: объединить несколько .Where () с предложением * OR * - PullRequest
13 голосов
/ 18 мая 2019

Я много искал о своей текущей проблеме, но я не мог найти реальный ответ, чтобы решить эту проблему.

Я пытаюсь создать запрос LINQ, который выдает следующий SQL:

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() вызовы создают запрос Linq, связанный с AND, что нормально.

Как разрешить им выполнять с круглыми скобками и добавить простой OR теперь с помощью API?

Есть ли способ сохранить предикат в некоторых переменных, чтобы я мог сделать что-то вроде:

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

или как решить эту проблему?

Я думаю, что для этой конкретной проблемы должно быть хорошее решение, и я не единственный, кому она нужна, но я абсолютно не уверен, как ее решить.

P.S .: Я не могу удалить несколько вызовов .Where(), и написание прямого SQL не вариант.

EDIT StackOverflow хочет, чтобы я сказал, почему мой вопрос отличается от других. Ну, дело в Parentheses. Я не хочу связывать все .Where() с одним предложением OR, я хочу оставить их с AND и добавить еще одно предложение OR, пока все запросы AND заключены в скобки.

Ответы [ 4 ]

7 голосов
/ 18 мая 2019

Если вы хотите построить свой запрос программно и выполнить его на своем 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);       
    }
}
4 голосов
/ 18 мая 2019

Сначала создайте несколько вспомогательных методов расширения, чтобы упростить объединение двух предикатов 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
0 голосов
/ 18 мая 2019

Хорошо, у вас есть свой собственный ответ о 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 голосов
/ 18 мая 2019

Вы можете использовать Expression для создания за один шаг, как это:

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)

Проверьте код в этой сущности: URL моей сущности

ОБНОВЛЕНИЕ 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.
...