Linq: додавання умов до пункту where умовно


103

У мене такий запит

(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 має додаткову умову. Чи є кращий спосіб зробити це?

Відповіді:


182

Якщо ви не зателефонували 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
Чи потрібно мені ставити всі умови де у викладі "var query = .."?
користувач20358

4
Наступні Де умови агрегуються як АБО або як А?
Vi100

4
@ vi100 вони будуть додатковими фільтрами, тож І
Рід Копсей

Слава богу за простоту! Мені так боляче бачити запити Linq на рівні 20+, коли вищезазначене набагато читабельніше
justagetherdev

Чому я отримую цю помилку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 фрукти = очікуйте db.Fruits .ConditionalWhere (() => color! = Null, f => f.Color == color) .ConditionalWhere (() => стиглий! = 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 і використовувати, якщо умови додають вирази до пункту де.


3

Просто я використовую його в моєму пункті, де

    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

На основі певних умов додайте, де умова ...

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

Ось мій код, щоб зробити подібну річ. Це метод на моєму веб-сервісі APF 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необов’язковий. Я використовую для перетворення об'єктів рядків бази даних в результати "фрукти".


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.

Я отримав відповідь, яку я шукав тут щодо об’єднання двох таблиць та запитів конкретних стовпців на будь-якій із таблиць

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.