2

この例では、MS が提供する NorthWind データベースを使用しています。

述語ビルダーを使用して複数を組み立てようとしています

Expression<Func<T,bool>> 

単一の式ツリーに。私の意見では、ほとんどうまく機能しますが、対処できないと思われる大きな欠陥が1つあります.

私は現在、そのように定義された2つの式を持っています:

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 のユニバーサル述語ビルダーを使用して、これら 2 つを次のように 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)))))));

問題は、本当に必要なのは 1 つだけだったのに、2 つの 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))))
    )
))

何らかの方法でナビゲーション パスを式として保存して再利用し、2 つの条件を適切なユーザー指定の演算子と値と共に挿入することはできますか?

または、いくつかの式のビジター実装を使用して...正確には何を見つけて置換するのかわかりませんか?

私のかなり長い質問を読んでくれてありがとう:)

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 に答える