如果在应用过滤器时存在结果,如何在LINQ to SQL表达式中应用过滤器?

时间:2011-02-14 19:37:46

标签: c# linq linq-to-sql expression-trees

我有一个函数我想转换成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表达式中应用这种过滤器?如果没有,有什么建议吗?

谢谢!罗伊

修改: 这是我想看的主要查询:

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()
    };

值得注意的是,Orders和Customers位于数据库服务器上的两个不同的数据库中,但它们都是在同一个DataContext中引用的。

3 个答案:

答案 0 :(得分:1)

对于您的原始方法,这可能会更好:

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;
}

这使得您只进行单个数据库往返。如果提供了产品区域ID,您将返回以下任何一个订单:

  • 原始查询中没有任何订单具有该区域ID或
  • 此订单确实具有该区域ID

它确实使查询更复杂,所以我会稍微测试一下它是否真的能给你带来任何性能提升。

这不会很好地转换为您建议的功能,但如果您分享有关如何调用此代码的更多信息,我可能会给您一些关于如何避免调用此函数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 :(得分:1)

也许是这样的:

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() 
    };

答案 2 :(得分:0)

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

以便将查询限制为最高优先级的订单。这将是次优的表现。