Условные запросы Linq

92

Мы работаем над программой просмотра журналов. У пользователя будет возможность фильтровать по пользователю, серьезности и т. Д. В дни Sql я бы добавил в строку запроса, но я хочу сделать это с помощью Linq. Как я могу условно добавить предложения where?

sgwill
источник

Ответы:

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 OR вместо AND ..?
Jon H
1
Да ... это немного сложно. Лучшее, что я видел, - это шаблон спецификации и включение предиката в спецификацию с последующим вызовом спецификации.Или (someOtherSpecification). По сути, вам нужно немного написать собственное дерево выражений. Пример кода и объяснение здесь: codeinsanity.com/archive/2008/08/13/…
Даррен Копп
У меня глупый вопрос: если эти журналы получены из базы данных, получаем ли мы все журналы, а затем фильтруем их в памяти? Если да, то как я могу передать условия в базу данных?
Али Умайр
это не фильтрация их в памяти. он создает запрос и отправляет все условия в базе данных (по крайней мере, для большинства поставщиков linq-to-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.
Али Умайр 01
23

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

    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 () не будет.

sgwill
источник
14

Что касается условного linq, мне очень нравятся паттерны фильтров и патрубков.
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, для сопоставления между объектами передачи данных и моделями Entity. Использование построителя предикатов позволит вам динамически изменять ваш 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")

означает, что когда создается окончательный запрос, if lastNameSearchэто falseзапрос полностью пропускает любой SQL для поиска по фамилии.

Джеймс Ливингстон
источник
Зависит от поставщика данных. LINQ-to-Entities не так хорошо его оптимизирует.
Suncat2000
1

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

ГДЕ Поле = @FieldVar ИЛИ @FieldVar ЕСТЬ NULL

Вы можете продублировать тот же стиль с помощью следующей лямбды (пример проверки аутентификации):

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

void RunQuery (string param1, string param2, int? param3) {

Func 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);

}

t3rse
источник
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")

Изменить: Ах, нужно прочитать более внимательно. Вы хотели знать, как условно добавить дополнительные предложения. В таком случае я понятия не имею. :) Я, наверное, просто подготовлю несколько запросов и выполню нужный, в зависимости от того, что мне в итоге нужно.

TheSmurf
источник
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 с предикатами в совершенно другой функции, которая объединяет все предикаты по ИЛИ.

Джон Лимджап
источник