Linq: условное добавление условий в предложение where

106

У меня есть такой запрос

(from u in DataContext.Users
       where u.Division == strUserDiv 
       && u.Age > 18
       && u.Height > strHeightinFeet  
       select new DTO_UserMaster
       {
         Prop1 = u.Name,
       }).ToList();

Я хочу добавить различные условия, такие как возраст, рост, в зависимости от того, были ли эти условия предоставлены методу, выполняющему этот запрос. Все условия будут включать Пользовательский Дивизион. Если был указан возраст, я хочу добавить это в запрос. Точно так же, если была указана высота, я хочу добавить и ее.

Если бы это было сделано с помощью sql-запросов, я бы использовал построитель строк, чтобы добавить их к основному запросу strSQL. Но здесь, в Linq, я могу думать только об использовании условия IF, при котором я напишу один и тот же запрос трижды, причем каждый блок IF будет иметь дополнительное условие. Есть лучший способ сделать это?

user20358
источник

Ответы:

186

Если вы не вызываете ToList()и ваше окончательное сопоставление с типом DTO, вы можете добавлять Whereпредложения по ходу работы и создавать результаты в конце:

var query = from u in DataContext.Users
   where u.Division == strUserDiv 
   && u.Age > 18
   && u.Height > strHeightinFeet
   select u;

if (useAge)
   query = query.Where(u => u.Age > age);

if (useHeight)
   query = query.Where(u => u.Height > strHeightinFeet);

// Build the results at the end
var results = query.Select(u => new DTO_UserMaster
   {
     Prop1 = u.Name,
   }).ToList();

Это по-прежнему приведет только к одному обращению к базе данных, что будет столь же эффективно, как и запись запроса за один проход.

Рид Копси
источник
1
Нужно ли мне указывать все условия where в операторе var query = ..?
user20358 05
4
Последующие условия Where объединяются как OR или как AND?
Vi100
4
@ vi100 они будут дополнительными фильтрами, так что И
Рид Копси
Слава богу за простоту! Мне так надоело видеть запросы Linq с более чем 20 строками, когда приведенное выше гораздо более читабельно
justanotherdev
Почему у меня LINQ to Entities does not recognize the method 'System.String get_Item(System.String)' method, and this method cannot be translated into a store expression.
появляется
19

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

public static IQueryable<T> ConditionalWhere<T>(
        this IQueryable<T> source, 
        Func<bool> condition,
        Expression<Func<T, bool>> predicate)
    {
        if (condition())
        {
            return source.Where(predicate);
        }

        return source;
    }

Это помогает избежать разрывов цепи. То же самое ConditionalOrderByи ConditionalOrderByDescendingполезно.

Юрий Грановский
источник
Полезно, но не могли бы вы добавить пример того, как это будет выглядеть при использовании.
Suncat2000
1
Должно получиться так: var fruit = await db.Fruits .ConditionalWhere (() => color! = Null, f => f.Color == color) .ConditionalWhere (() => ripe! = Null, f => f .Ripe == спелые) .ToListAsync ();
Юрий Грановский
4
Прекрасно работает! Спасибо! Я также сделал перегрузку для условия в виде простого логического значения вместо функции, чтобы сделать его более интуитивно понятным, когда делегат добавит ненужную сложность. Я довольно часто использую этот метод расширения и очень ценю ваше решение.
Suncat2000
18

один вариант.

bool? age = null

(from u in DataContext.Users
           where u.Division == strUserDiv 
           && (age == null || (age != null && u.Age > age.Value))
           && u.Height > strHeightinFeet  
           select new DTO_UserMaster
           {
             Prop1 = u.Name,
           }).ToList();

или вы можете переключиться на синтаксис метода для linq и использовать условия if для присоединения выражений к предложению where.

Мэтью Вайнс
источник
4

Просто я использую его в своем предложении where как

    public IList<ent_para> getList(ent_para para){
     db.table1.Where(w=>(para.abc!=""?w.para==para.abc:true==true) && (para.xyz!=""?w.xyz==para.xyz:true==true)).ToList();
}
Своя
источник
3

На основе определенного условия добавьте условие where ...

from u in DataContext.Users
where u.Division == strUserDiv 
&& u.Age != null ? u.Age > 18 : 1== 1
&& u.Height != null ? u.Height > 18 : 1== 1
&& u.Height != null ? u.Height > 18 : 1== 1
 select new DTO_UserMaster
       {
         Prop1 = u.Name,
       }).ToList();
Мелу
источник
2

Вот мой код, чтобы сделать то же самое. Это метод в моем API веб-службы WCF SOAP.

    public FruitListResponse GetFruits(string color, bool? ripe)
    {
        try
        {
            FruitContext db = new FruitContext();
            var query = db.Fruits.Select(f => f);
            if (color != null)
            {
                query = query.Where(f => f.Color == color);
            }
            if (ripe != null)
            {
                query = query.Where(f => f.Ripe == ripe);
            }
            return new FruitListResponse
            {
                Result = query.Select(f => new Fruit { Id = f.FruitId, Name = f.Name }).ToList()
            };
        }
        catch (Exception e)
        {
            return new FruitListResponse { ErrorMessage = e.Message };
        }
    }

Базовый запрос Select(f => f)означает в основном ВСЕ, и к нему Whereнеобязательно могут быть прикреплены предложения. Финал не Selectявляется обязательным. Я использую для преобразования объектов строк базы данных в объекты результата "Fruit".

Джон Хенкель
источник
0

Предполагая следующий параметр,

Int? Age = 18;

Просто используя &&и ||условные операторы мы можем иметь другую версию.

(from u in DataContext.Users
where u.Division == strUserDiv 
    && (Age == null || u.Age > Age)
    && (Param1 == null || u.param1 == Param1)
    && u.Height > strHeightinFeet
select new DTO_UserMaster
{
    Prop1 = u.Name,
}).ToList();

Как и Param1, вы можете добавить любое количество параметров для условия поиска.

Сушант Йелпале
источник
0

Я просто наткнулся на это в поисках чего-то еще, но подумал, что добавлю лямбда-версию.

Во-первых, я бы создал такой класс, чтобы передавать параметры на уровень данных:

   public class SearchParameters() {
       public int? Age {get; set;}
       public string Division {get;set;}
       etc
    }

Затем на моем уровне данных что-то вроде этого:

public IQueryable<User> SearchUsers(SearchParameters params) 
{
    var query = Context.Users;
    if (params.Age.HasValue)
    {
         query = query.Where(u => u.Age == params.Age.Value);
    }
    if (!string.IsNullOrEmpty(params.Division)
    {
        query = query.Where(u => u.Division == params.Division);
    }
    etc
    return query;
}

Где вы материализуете вопрос, зависит от вас. Может быть слой между приложением и данными, который преобразует представления, специфичные для db, в db-agnostic (возможно, вы запрашиваете несколько источников данных). Этот слой может получать несколько типов запросов из этих источников и отображать их, например, в общее представление POCO.

Скотт Петерсон
источник
Ой, я не видел ответа Джона Хенкеля. Та же идея.
Скотт Петерсон
0

Просто чтобы добавить к принятому выше ответу здесь , если вы выполняете динамический поиск по объединению, подумайте о том, чтобы вернуть новый объект с обеими таблицами (t1, t2) в исходном запросе linq, чтобы вы могли получить к ним доступ индивидуально для выполнения условного поиск.

var query = from t1 in _context.Table1
            join t2 in _context.Table2 on t1.Table1Id equals t2.Table1IdId
            select new { t1, t2 };

        if (!string.IsNullOrEmpty(searchProperty1))
        {
            query = query.Where(collection => collection.t1.TableColumn == searchProperty1);
        }
        if (!string.IsNullOrEmpty(searchProperty2))
        {
            query = query.Where(collection => collection.t2.TableColumn == searchProperty2);
        }
        ....etc.

Я получил ответ, который искал здесь, относительно объединения двух таблиц и запроса определенных столбцов в любой из таблиц.

Риаан Сайман
источник