2

在这个例子中,我使用的是 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));

我使用来自 Pete Montgomery 的通用谓词构建器来 OR 这两者,如下所示:

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

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

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

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

4

1 回答 1

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);
于 2013-09-12T21:24:01.960 回答