Умовні запити Linq


92

Ми працюємо над засобом перегляду журналів. Використання матиме можливість фільтрувати за користувачем, ступенем тяжкості тощо. За днів Sql я додав би до рядка запиту, але я хочу зробити це за допомогою Linq. Як я можу умовно додати де-речення?

Відповіді:


156

якщо ви хочете фільтрувати лише за умови передачі певних критеріїв, зробіть щось подібне

var logs = from log in context.Logs
           select log;

if (filterBySeverity)
    logs = logs.Where(p => p.Severity == severity);

if (filterByUser)
    logs = logs.Where(p => p.User == user);

Якщо зробити це таким чином, дерево виразів зможе бути саме тим, що ви хочете. Таким чином створений SQL буде саме тим, що вам потрібно, і не менше.


2
Привіт. Чи є у вас пропозиції щодо створення пропозицій where АБО замість ANDs ..?
Jon H

1
Так ... це трохи важко зробити. Найкраще, що я бачив, - це шаблон специфікації та втягування предиката в специфікацію, а потім виклик специфікації. Або (someOtherSpecification). В основному вам потрібно трохи написати власне дерево виразів. Приклад коду та пояснення тут: codeinsanity.com/archive/2008/08/13/…
Даррен Копп,

У мене дурне запитання: якщо ці журнали отримуються з бази даних, чи отримуємо ми всі журнали, а потім фільтруємо їх у пам’яті? Якщо так, то як я можу передати умови до бази даних
Алі Умайр,

це не фільтрування їх у пам’яті. він створює запит і надсилає всі умови в базі даних (принаймні для більшості постачальників посилань-на-x)
Даррен Копп,

отримання цієї помилки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.
Алі Умайр

22

Якщо вам потрібно відфільтрувати основу за списком / масивом, використовуйте наступне:

    public List<Data> GetData(List<string> Numbers, List<string> Letters)
    {
        if (Numbers == null)
            Numbers = new List<string>();

        if (Letters == null)
            Letters = new List<string>();

        var q = from d in database.table
                where (Numbers.Count == 0 || Numbers.Contains(d.Number))
                where (Letters.Count == 0 || Letters.Contains(d.Letter))
                select new Data
                {
                    Number = d.Number,
                    Letter = d.Letter,
                };
        return q.ToList();

    }

3
Це, безумовно, найкраща і правильна відповідь. Умовна || лише порівнює першу частину і пропускає другу, якщо перша частина правдива ... гарно зроблено!
Сердж Саган

1
Ця конструкція включає частину виразу 'або' у згенерованому запиті SQL. Прийнята відповідь породить більш ефективні твердження. Звичайно, залежно від оптимізації постачальника даних. LINQ-to-SQL може мати кращу оптимізацію, але LINQ-to-Entities - ні.
Suncat2000

20

Я закінчив, використовуючи відповідь, схожу на відповідь Дарена, але з інтерфейсом IQueryable:

IQueryable<Log> matches = m_Locator.Logs;

// Users filter
if (usersFilter)
    matches = matches.Where(l => l.UserName == comboBoxUsers.Text);

 // Severity filter
 if (severityFilter)
     matches = matches.Where(l => l.Severity == comboBoxSeverity.Text);

 Logs = (from log in matches
         orderby log.EventTime descending
         select log).ToList();

Це створює запит перед потраплянням у базу даних. Команда не запускатиметься до кінця .ToList ().


14

Що стосується умовної лінки, я дуже люблю фільтри та схему труб.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

В основному ви створюєте метод розширення для кожного випадку фільтра, який приймає IQueryable та параметр.

public static IQueryable<Type> HasID(this IQueryable<Type> query, long? id)
{
    return id.HasValue ? query.Where(o => i.ID.Equals(id.Value)) : query;
}

8

Я вирішив це методом розширення, щоб дозволити LINQ умовно активувати в середині вільного виразу. Це позбавляє від необхідності розбивати вираз ifтвердженнями.

.If() метод розширення:

public static IQueryable<TSource> If<TSource>(
        this IQueryable<TSource> source,
        bool condition,
        Func<IQueryable<TSource>, IQueryable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }

Це дозволяє зробити це:

return context.Logs
     .If(filterBySeverity, q => q.Where(p => p.Severity == severity))
     .If(filterByUser, q => q.Where(p => p.User == user))
     .ToList();

Ось також IEnumerable<T>версія, яка буде обробляти більшість інших виразів LINQ:

public static IEnumerable<TSource> If<TSource>(
    this IEnumerable<TSource> source,
    bool condition,
    Func<IEnumerable<TSource>, IEnumerable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }

4

Іншим варіантом було б використовувати щось на зразок PredicateBuilder, про який тут мова . Це дозволяє писати код таким чином:

var newKids  = Product.ContainsInDescription ("BlackBerry", "iPhone");

var classics = Product.ContainsInDescription ("Nokia", "Ericsson")
                  .And (Product.IsSelling());

var query = from p in Data.Products.Where (newKids.Or (classics))
            select p;

Зверніть увагу, що я маю це лише для роботи з Linq 2 SQL. EntityFramework не реалізує Expression.Invoke, необхідний для роботи цього методу. У мене є запитання щодо цього питання тут .


Це чудовий метод для тих, хто використовує бізнес-логічний шар поверх свого сховища, а також такий інструмент, як AutoMapper, для зіставлення об'єктів передачі даних та моделей сутності. Використання конструктора предикатів дозволить вам динамічно модифікувати ваш IQueryable перед тим, як відправити його в AutoMapper для вирівнювання, тобто внесення списку в пам’ять. Зауважте, що він також підтримує Entity Framework.
chrisjsherm

3

Робимо це:

bool lastNameSearch = true/false; // depending if they want to search by last name,

маючи це у whereзаяві:

where (lastNameSearch && name.LastNameSearch == "smith")

означає, що при створенні остаточного запиту, якщо lastNameSearchє, falseзапит повністю пропускає будь-який SQL для пошуку за прізвищем.


Залежить від постачальника даних. LINQ-to-Entities не так оптимізує його.
Suncat2000

1

Це не найкрасивіша річ, але ви можете використовувати лямбда-вираз і передавати свої умови за бажанням. У TSQL я роблю багато наступного, щоб зробити параметри необов’язковими:

WHERE Field = @FieldVar АБО @FieldVar НУЛЬНИЙ

Ви можете скопіювати той самий стиль за допомогою такої лямбда (приклад перевірки автентичності):

MyDataContext db = новий MyDataContext ();

void RunQuery (рядок param1, рядок param2, int? param3) {

Функція checkUser = користувач =>

((param1.Length> 0)? user.Param1 == param1: 1 == 1) &&

((param2.Length> 0)? user.Param2 == param2: 1 == 1) &&

((param3! = null)? user.Param3 == param3: 1 == 1);

Користувач foundUser = db.Users.SingleOrDefault (checkUser);

}


1

Нещодавно я мав подібну вимогу, і врешті-решт виявив це в MSDN. Приклади CSharp для Visual Studio 2008

Класи, включені до зразка DynamicQuery завантаження, дозволяють створювати динамічні запити під час виконання у наступному форматі:

var query =
db.Customers.
Where("City = @0 and Orders.Count >= @1", "London", 10).
OrderBy("CompanyName").
Select("new(CompanyName as Name, Phone)");

Використовуючи це, ви можете динамічно створювати рядок запиту під час виконання та передавати його в метод Where ():

string dynamicQueryString = "City = \"London\" and Order.Count >= 10"; 
var q = from c in db.Customers.Where(queryString, null)
        orderby c.CompanyName
        select c;

1

Ви можете створити та використовувати цей метод розширення

public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool isToExecute, Expression<Func<TSource, bool>> predicate)
{
    return isToExecute ? source.Where(predicate) : source;
}

0

Просто використовуйте оператор && C #:

var items = dc.Users.Where(l => l.Date == DateTime.Today && l.Severity == "Critical")

Редагувати: Ах, потрібно читати уважніше. Ви хотіли знати, як умовно додати додаткові речення. У такому випадку я поняття не маю. :) Що б я, мабуть, зробив, це просто підготувати кілька запитів і виконати правильний, залежно від того, що мені в підсумку було потрібно.


0

Ви можете використовувати зовнішній метод:

var results =
    from rec in GetSomeRecs()
    where ConditionalCheck(rec)
    select rec;

...

bool ConditionalCheck( typeofRec input ) {
    ...
}

Це буде працювати, але не може бути розбито на дерева виразів, що означає, що Linq to SQL запускає перевірочний код для кожного запису.

В якості альтернативи:

var results =
    from rec in GetSomeRecs()
    where 
        (!filterBySeverity || rec.Severity == severity) &&
        (!filterByUser|| rec.User == user)
    select rec;

Це може працювати в деревах виразів, тобто Linq to SQL буде оптимізовано.


0

Ну, я подумав, що ви можете помістити умови фільтру в загальний список предикатів:

    var list = new List<string> { "me", "you", "meyou", "mow" };

    var predicates = new List<Predicate<string>>();

    predicates.Add(i => i.Contains("me"));
    predicates.Add(i => i.EndsWith("w"));

    var results = new List<string>();

    foreach (var p in predicates)
        results.AddRange(from i in list where p.Invoke(i) select i);               

У результаті з’являється список, що містить „я”, „мені” та „косити”.

Ви можете оптимізувати це, виконавши foreach з предикатами в абсолютно іншій функції, що АБО всі предикати.

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