Условные запросы Linq
-
08-06-2019 - |
Вопрос
Мы работаем над средством просмотра журналов.Использование будет иметь возможность фильтровать по пользователю, серьезности и т. д.Во времена Sql я бы добавлял к строке запроса, но я хочу сделать это с помощью Linq.Как я могу условно добавить предложения-где?
Решение
если вы хотите фильтровать только те критерии, которые заданы, сделайте что-то вроде этого
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 будет именно тем, что вам нужно, и не меньше.
Другие советы
Если вам нужно отфильтровать базу по списку/массиву, используйте следующее:
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();
}
Я закончил использовать ответ, похожий на ответ Дарена, но с интерфейсом 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().
Когда дело доходит до условного 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;
}
Другой вариант — использовать что-то вроде обсуждаемого 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, необходимый для работы этого метода.У меня есть вопрос по этому вопросу здесь.
Делая это:
bool lastNameSearch = true/false; // depending if they want to search by last name,
имея это в where
заявление:
where (lastNameSearch && name.LastNameSearch == "smith")
означает, что при создании окончательного запроса, если lastNameSearch
является false
запрос будет полностью пропускать SQL-запрос для поиска по фамилии.
Я решил эту проблему с помощью метода расширения, позволяющего условно включать LINQ в середине беглого выражения.Это устраняет необходимость разбивать выражение на if
заявления.
.If()
метод расширения:
public static IQueryable<TSource> If<TSource>(
this IQueryable<TSource> source,
bool condition,
Func<IQueryable<TSource>, IQueryable<TSource>> branch)
{
return condition ? source : branch(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 ? source : branch(source);
}
Это не самая красивая вещь, но вы можете использовать лямбда-выражение и при необходимости передавать свои условия.В TSQL я делаю множество следующих действий, чтобы сделать параметры необязательными:
ГДЕ Поле = @FieldVar ИЛИ @FieldVar НУЛЕВОЕ
Вы можете продублировать тот же стиль со следующей лямбдой (пример проверки аутентификации):
MyDataContext db = новый MyDataContext();
void RunQuery (строка param1, строка param2, int?параметр3){
Проверка функцийПользователь = пользователь =>
((параметр1.Длина > 0)?пользователь.Парам1 == параметр1:1 == 1) &&
((param2.Length > 0)?пользователь.Парам2 == параметр2:1 == 1) &&
((param3 != ноль)?user.Param3 == param3 :1 == 1);
Пользователь FoundUser = db.Users.SingleOrDefault(checkUser);
}
Недавно у меня было подобное требование, и в конце концов я нашел его в 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;
Просто используйте оператор && C#:
var items = dc.Users.Where(l => l.Date == DateTime.Today && l.Severity == "Critical")
Редактировать:Ох, надо читать внимательнее.Вы хотели знать, как условно добавить дополнительные положения.В таком случае я понятия не имею.:) Я бы, вероятно, просто подготовил несколько запросов и выполнил правильный, в зависимости от того, что мне в конечном итоге нужно.
Вы можете использовать внешний метод:
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 будет оптимизирован.
Я подумал, что можно поместить условия фильтра в общий список предикатов:
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 с предикатами в совершенно другой функции, которая выполняет ИЛИ все предикаты.
Вы можете создать и использовать этот метод расширения
public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool isToExecute, Expression<Func<TSource, bool>> predicate)
{
return isToExecute ? source.Where(predicate) : source;
}