#c# #.net #linq-to-sql
#c# #.net #linq-to-sql
Вопрос:
У меня есть довольно сложный запрос, который мне нужно написать. Я надеюсь сделать это, используя Linq to Sql, если это возможно. База данных выглядит примерно так:
Клиенты (Имя, Фамилия, идентификационный номер, Пол)
Заказы (Дата, Количество, Вес, название товара, Цена)
Адрес (Город, штат, почтовый индекс)
Запрос будет заключаться в том, чтобы позволить пользователю выполнять поиск по любому из этих полей, а в случае числовых полей — выполнять поиск <, = или > по своему усмотрению.
Что-то вроде этого было бы примером запроса, который я должен реализовать:
Запрос 1: Выберите клиентов, у которых имя = «Джон» и у которых есть хотя бы один заказ (Вес> 40 или Количество> 10 ИЛИ Цена> 5) и почтовый индекс = 12345.
Запрос 2: Выберите клиентов, у которых имя = ‘John’ и есть хотя бы один заказ с весом <20 и имя_элемента = ‘widget’ и количество = 10) и почтовый индекс = 12345.
Я могу получить основную часть поиска клиентов, но я застрял на поиске в таблице заказов, где пользователь может указать <>= способом OR.
query = Context.Customers.AsQueryable();
if (searchingFirstName) query = query.Where(cust => cust.First == firstName);
if (searchingLastName) query = query.Where(cust => cust.Last == lastName);
if (searchingZip) query = query.Where(cust => cust.Address.Zip == zip);
// using dynamic Linq
if (searchingGender) query = query.Where("Gender == @0", gender);
// how do I search the Orders? The dynamic linq functions appear
// to only work on the top level table
Ответ №1:
Вы могли бы использовать PredicateBuilder из LinqKit. Он добавляет некоторые новые методы расширения для предикатных лямбд:
var predicate = PredicateBuilder.True<Customer>();
if (searchingFirstName)
{
predicate = predicate.And(cust => cust.First == firstName);
}
if (searchingOrders)
{
// Some code to unify the .And() and .Or() cases
Expression<Func<Order, bool>> subpredicate;
Func<Expression<Func<Order, bool>>, Expression<Func<Order, bool>>, Expression<Func<Order, bool>>> joiner;
if (orderMethodAny)
{
subpredicate = PredicateBuilder.True<Order>();
joiner = PredicateBuilder.And;
}
else
{
subpredicate = PredicateBuilder.False<Order>();
joiner = PredicateBuilder.Or;
}
if (searchingOrderDate)
{
// ...
}
if (searchingOrderWeight)
{
switch (orderOp)
{
case Op.Less:
subpredicate = joiner(subpredicate, ord => ord.Weight < orderWeight);
break;
case Op.LessEqual:
subpredicate = joiner(subpredicate, ord => ord.Weight <= orderWeight);
break;
case Op.Equal:
subpredicate = joiner(subpredicate, ord => ord.Weight == orderWeight);
break;
case Op.GreaterEqual:
subpredicate = joiner(subpredicate, ord => ord.Weight >= orderWeight);
break;
case Op.Greater:
subpredicate = joiner(subpredicate, ord => ord.Weight > orderWeight);
break;
case Op.NotEqual:
subpredicate = joiner(subpredicate, ord => ord.Weight != orderWeight);
break;
}
}
if (searchingOrderQuantity)
{
// ...
}
if (searchingOrderItemName)
{
// ...
}
if (searchingOrderPrice)
{
// ...
}
predicate = predicate.And(cust => cust.Orders.Any(subpredicate));
}
if (searchingZipCode)
{
predicate = predicate.And(cust => cust.ZipCode == zipCode);
}
var query = Context.Customers.Where(predicate);
Возможно, вам потребуется вызвать .Expand()
предикаты, прежде чем передавать их в качестве аргументов, или .AsExpandable()
запрос, если вы используете Entity Framework.
Комментарии:
1. Большое спасибо. Отличный ответ. Я должен был позвонить . Compile() для субпредиката. предикат = предикат. И(cust => cust. Приказы. Любой (субпредикат. Compile()); К сожалению, теперь я понимаю, что мой вопрос был не совсем правильным.
2. .Compile() преобразует выражение в делегат. Это заставило бы систему выполнять фильтрацию на клиенте, а не в базе данных.
3. После переписывания его для использования реальных запросов мне больше не нужны никакие Expand(), AsExpandable() или Compile() . Но он прекрасно работает.