Как применить фильтр в выражении LINQ to SQL, только если при применении фильтра существуют результаты? - PullRequest
1 голос
/ 14 февраля 2011

У меня есть функция, которую я хотел бы преобразовать в выражение LINQ to SQL, но я не могу понять, как это сделать. Эта функция вызывается из запроса LINQ, один раз для каждой строки в наборе результатов. Передаваемый productAreaId может указывать или не ссылаться на действительные данные, поэтому я должен проверять, а затем фильтровать только по productAreaId, если после применения фильтра есть строки:

//Forgive the contrived example...
public static IQueryable<Order> GetOrders(int orderNumber, int? productAreaId, 
    OSDataContext db)
{
    var orders = db.Orders.Where(o => o.OrderNumber == orderNumber &&
        o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
        !o.Deleted);

    if (productAreaId != null)
    {
        var orders2 = orders.Where(o => o.ProductAreaId == productAreaId);
        if (orders2.Any()) return orders2;
    }
    return orders;
}

Я не хочу делать это так. Мне нужна функция, которая возвращает выражение без произвольного кода, поэтому оно будет составным. Вышеуказанная функция отображается только потому, что я знаю, как это можно заключить в функцию. Это единственный способ.

Я бы хотел сделать что-то вроде этого, заменив надуманный ApplyFilterIfAnyResultExists чем-то, что действительно работает:

public static Expression<Func<Order,bool>> 
    GetOrdersExpr(int orderNumber, int? productAreaId)
{
    return o => o.OrderNumber == orderNumber && 
        o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
        !o.Deleted && (productAreaId == null || 

            //Making up a fictional function. Is there a real way to do this?
            o.ApplyFilterIfAnyResultExists(row => 
                row.ProductAreaId == productAreaId)
        );
}

Есть ли способ применить этот тип фильтра в выражении LINQ to SQL? Если нет, какие-либо предложения?

Спасибо!
Рой

EDIT: Это основной запрос, который я хотел бы посмотреть:

var customerData = 
    from c in db.Customers
    select new 
    {
        id = c.Id,
        name = c.Name,

        lastOrder =
            db.Orders
            .Where(GetOrdersExpr(c.LastOrderNumber, 
                c.PreferredProductAreaId))
            .FirstOrDefault(),

        allOrders = c.OrderForms
            .Select(form => 
                db.Orders
                .Where(GetOrdersExpr(form.OrderNumber,
                    c.PreferredProductAreaId))
                .FirstOrDefault()
            )
            .Where(o => o != null)

        //How lastOrder used to be queried
        //lastOrder =
        //    GetOrders(c.LastOrderNumber, c.PreferredProductAreaId, db)
        //    .FirstOrDefault()
    };

Стоит также отметить, что Заказы и Клиенты находятся в двух разных базах данных на сервере базы данных, но здесь они оба ссылаются на один и тот же DataContext.

Ответы [ 3 ]

1 голос
/ 14 февраля 2011

Для вашего оригинального метода это может работать лучше:

public static IQueryable<Order> GetOrders(int orderNumber, int? productAreaId, 
    OSDataContext db)
{
    var orders = db.Orders.Where(o => o.OrderNumber == orderNumber &&
        o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
        !o.Deleted);
    if(productAreaId != null)
    {
        orders = orders.Where(
            o => !orders.Any(o2 => o2.ProductAreaId == productAreaId) ||
                    o.ProductAreaId == productAreaId);
    }
    return orders;
}

Это позволяет сделать так, чтобы вы выполняли только одну поездку по базе данных. Если указан идентификатор области продукта, вы будете возвращать заказы, где:

  • ни один из заказов в исходном запросе не имеет идентификатора области, или
  • этот заказ имеет идентификатор области

Это действительно усложняет запрос, поэтому я немного протестирую его, чтобы увидеть, действительно ли он дает вам прирост производительности.

Это не очень хорошо отразится на предложенной вами функции, но если вы поделитесь дополнительной информацией о том, как вызывается этот код, я мог бы дать вам несколько советов о том, как избежать вызова этой функции более 20 раз .

Редактировать

Примерно так должно работать:

var customerData = 
    from c in db.Customers
    let productAreaId = c.PreferredProductAreaId
    let orders = 
        db.Orders
        .Where(o => o.OrderNumber == c.LastOrderNumber &&
            o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
            !o.Deleted)
        .OrderBy(o => o.Date)
    let lastOrderInProductArea = productAreaId != null
        ? orders.FirstOrDefault(o => o.ProductAreaId == productAreaId)
        : null
    select new 
    {
        id = c.Id,
        name = c.Name,
        lastOrder = lastOrderInProductArea != null
            ? lastOrderInProductArea
            : orders.FirstOrDefault()
    };
1 голос
/ 14 февраля 2011

Может быть, что-то вроде этого:

var customerData = 
    from c in db.Customers
    let orders = db.Orders.Where(o => o.OrderNumber == c.orderNumber &&
        o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
        !o.Deleted)
    let orders2 = orders.Where(o => o.ProductAreaId == c.productAreaId)
    select new 
    {
        id = c.Id,
        name = c.Name,
        lastOrder = c.productAreaId != null && orders2.Any() ?
            orders2.FirstOrDefault() :
            orders.FirstOrDefault() 
    };
0 голосов
/ 14 февраля 2011
public static IQueryable<Order> GetOrders(int orderNumber, int? productAreaId, OSDataContext db)
{
    var orders = db.Orders.Where(o => o.OrderNumber == orderNumber &&
        o.Group.GroupTypeId != (int)GroupTypeId.INTERNAL &&
        !o.Deleted);


    if (productAreaId != null)
    {
        var orders2 = orders.Where(o => o.ProductAreaId == productAreaId);
return orders2.Select(x => new {x, Type = 2 }).Concat(orders.Select(x => new {x, Type = 1 })).OrderBy(x => x.Type);
    }
    return orders;
}

При этом оба результата будут объединены. Сначала результаты от orders2, а затем от orders2. Это может помочь вам.

Если вы действительно хотите заказать только одну группу, вы можете сделать

GetOrders().Where(x => x.Type == GetOrders().Max(x => x.Type))

, чтобы ограничить запрос заказами с наивысшим приоритетом. Это будет иметь неоптимальную производительность.

...