使用PredicateBuilder时如何防止多个连接到同一个表

时间:2013-09-11 23:54:26

标签: c#

在本例中,我使用的是MS提供的NorthWind数据库。

我正在尝试使用谓词构建器来组合多个

Expression<Func<T,bool>> 

进入单个表达式树。在我看来,它大部分都很有效,但有一个我无法解决的重大缺陷。

我目前有两个定义如下的表达式:

Expression<Func<Customer, bool>> UnitPriceLessThan2 = c => 
        c.Orders.Any(o => o.Order_Details.Any(d => d.Product.UnitPrice <= 2));

Expression<Func<Customer, bool>> UnitPriceGreaterThan5 = c => 
        c.Orders.Any(o => o.Order_Details.Any(d => d.Product.UnitPrice >= 5));

我使用universal predicate builder from Pete Montgomery将这两者组合在一起如下:

Expression<Func<Customer,bool>> PriceMoreThan5orLessThan2 = 
         UnitPriceLessThan2.Or(UnitPriceGreaterThan5);

这两个表达式都需要通过相同的路径导航到Product实体,因此对两个条件重用相同的子查询是有意义的。如果我只是手动编写条件,它看起来像这样:

Expression<Func<Customer,bool>> PriceMoreThan5orLessThan2 = c => 
        c.Orders.Any(o => 
            o.Order_Details.Any(d => d.Product.UnitPrice >= 5 || 
                                d.Product.UnitPrice <= 2));

但是,由于动态构建这些谓词的要求,我不能这样做,因为会有数百种可能的组合......或更多。

所以我的问题是如何阻止LINQ to Entities创建这样的查询:

SELECT 
/*all the customer columns*/
FROM [dbo].[Customers] AS [Extent1]
WHERE ( EXISTS (SELECT 
    1 AS [C1]
    FROM ( SELECT 
        [Extent2].[OrderID] AS [OrderID]
        FROM [dbo].[Orders] AS [Extent2]
        WHERE [Extent1].[CustomerID] = [Extent2].[CustomerID]
    )  AS [Project1]
    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM  [dbo].[Order Details] AS [Extent3]
        INNER JOIN [dbo].[Products] AS [Extent4] ON [Extent3].[ProductID] = [Extent4].[ProductID]
        WHERE ([Project1].[OrderID] = [Extent3].[OrderID]) AND ([Extent4].[UnitPrice] <= cast(2 as decimal(18)))
    )
)) OR ( EXISTS (SELECT 
    1 AS [C1]
    FROM ( SELECT 
        [Extent5].[OrderID] AS [OrderID]
        FROM [dbo].[Orders] AS [Extent5]
        WHERE [Extent1].[CustomerID] = [Extent5].[CustomerID]
    )  AS [Project4]
    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM  [dbo].[Order Details] AS [Extent6]
        INNER JOIN [dbo].[Products] AS [Extent7] ON [Extent6].[ProductID] = [Extent7].[ProductID]
        WHERE ([Project4].[OrderID] = [Extent6].[OrderID]) AND (([Extent7].[UnitPrice] >= cast(5 as decimal(18)))))));

问题在于,当我们真的只需要一个时,我们创建了两个EXISTS子查询。

相反,我希望查询看起来像这样:

SELECT 
/*all the customer columns*/
FROM [dbo].[Customers] AS [Extent1]
WHERE( EXISTS (SELECT 
    1 AS [C1]
    FROM ( SELECT 
        [Extent5].[OrderID] AS [OrderID]
        FROM [dbo].[Orders] AS [Extent5]
        WHERE [Extent1].[CustomerID] = [Extent5].[CustomerID]
    )  AS [Project4]
    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM  [dbo].[Order Details] AS [Extent6]
        INNER JOIN [dbo].[Products] AS [Extent7] ON [Extent6].[ProductID] = [Extent7].[ProductID]
        WHERE ([Project4].[OrderID] = [Extent6].[OrderID]) AND (([Extent7].[UnitPrice] >= cast(5 as decimal(18))) OR ([Extent7].[UnitPrice] <= cast(2 as decimal(18))))
    )
))

我可以以某种方式存储和重用导航路径作为表达式,然后将两个条件与适当的用户提供的运算符和值一起注入其中吗?

或者使用一些表达式访问者实现...我不知道究竟是什么,找到并替换?

感谢您阅读我相当冗长的问题:)

1 个答案:

答案 0 :(得分:0)

首先关闭一个提高可读性的小技巧,在命名空间中添加这些,不需要这个才能工作,但我发现它使代码更容易阅读:

namespace YourNameSpaceHere
{
    using DetailPredicate = Expression<Func<Order_Details, bool>>;
    using CustomerPredicate = Expression<Func<Customer, bool>>;

其次,创建一个Order Detail谓词列表,当添加了所有可能的Order Detail谓词时,我们将它们聚合并应用它们:

public void foo()
{
  // List of predicates for order detail
  var predicates = new List<DetailPredicate>();

  // Logic to determine what predicates get added to list
  if(somelogic)
    predicates.Add(d => d.Product.UnitPrice >= 5);

  if(somethingelse)    
    predicates.Add(d => d.Product.UnitPrice <= 2);

  // Default to true
  var whereDetails = PredicateBuilder.True<Order_Details>();

  if (predicates.Any())
  {
     // Aggregate predicates with OR in between
     whereDetails = predicates.Aggregate(PredicateBuilder.Or);
  }

  // Apply aggregate
  CustomerPredicate PriceMoreThan5orLessThan2 = c => 
      c.Orders.Any(o => 
          o.Order_Details.Any(whereDetails);