Запрос дочерних таблиц с помощью динамической ссылки на sql?

#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() . Но он прекрасно работает.