在本例中,我使用的是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))))
)
))
我可以以某种方式存储和重用导航路径作为表达式,然后将两个条件与适当的用户提供的运算符和值一起注入其中吗?
或者使用一些表达式访问者实现...我不知道究竟是什么,找到并替换?
感谢您阅读我相当冗长的问题:)
答案 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);