1. ホーム
  2. c#

[解決済み] EF Coreのインクルードでのフィルタリング

2022-09-29 02:08:54

質問

最初のクエリでフィルタリングしようとしています。私はモデルからネストされたインクルードのリーフを持っています。私は、インクルードの1つのプロパティに基づいてフィルタリングしようとしています。例えば

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
            .ThenInclude(post => post.Author)
        .ToList();
}

また、どのようにすれば .Where(w => w.post.Author == "me") ?

どのように解決するのですか?

Entity Framework core 5 は、EF の最初のバージョンで をサポートする最初の EF バージョンです。 Include .

動作の説明

対応する操作

  • Where
  • OrderBy(Descending)/ThenBy(Descending)
  • Skip
  • Take

いくつかの使用例( オリジナルの機能要求 github コミット ) :

ナビゲーションごとに1つのフィルタしか許可されないため、同じナビゲーションを複数回含める必要がある場合(同じナビゲーションに複数の ThenInclude など)、フィルタは1回だけ適用するか、そのナビゲーションに対してまったく同じフィルタを適用します。

context.Customers
    .Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.OrderDetails)
    .Include(c => c.Orders).ThenInclude(o => o.Customer)

または

context.Customers
    .Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.OrderDetails)
    .Include(c => c.Orders.Where(o => o.Name != "Foo")).ThenInclude(o => o.Customer)

もう一つの重要な注意点

新しいフィルタ操作を使用してインクルードされたコレクションは、ロードされたものとみなされます。

つまり、レイジーローディングが有効な場合、ある顧客のアドレスの Orders コレクションに対応しても、全体の再読み込みは行われません。 Orders コレクション全体の再読み込みをトリガーしません。

また、後続の2つのフィルタリングされた Include が同じコンテキストにあると、その結果が蓄積されます。例えば...

context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))

...の後に...

context.Customers.Include(c => c.Orders.Where(o => o.IsDeleted))

...は、結果として customersOrders コレクションがあり、すべての注文が含まれています。

フィルタリングされたインクルードとリレーションシップの修正

もし他の Order が同じコンテキストに読み込まれた場合、より多くのものが customers.Orders コレクションに追加されるかもしれません。 リレーションシップフィクスアップ . これは、EF の変更追跡機能がどのように動作するかのため、必然的なことです。

context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))

...の後に...

context.Orders.Where(o => o.IsDeleted).Load();

...は、またしても customersOrders コレクションがあり、すべての注文が含まれています。

フィルタ式

フィルタ式は述語を含んでいなければならず、その述語は スタンドアローン として使える述語を含むべきです。例によって、このことが明らかになります。のプロパティでフィルタリングされたオーダーを含めたいとします。 Customer :

context.Customers.Include(c => c.Orders.Where(o => o.Classification == c.Classification))

コンパイルはできますが、非常に技術的な実行時例外が発生し、基本的には o.Classification == c.Classification が翻訳できないのは c.Classification が見つからないからです。の後方参照を使ってクエリを書き直さなければなりません。 Order から Customer :

context.Customers.Include(c => c.Orders.Where(o => o.Classification == o.Customer.Classification))

述語は o => o.Classification == o.Customer.Classification) は、quot;stand alone"であり、フィルタリングのために使われることができます。 Orders を独立してフィルタリングすることができます。

context.Orders.Where(o => o.Classification == o.Customer.Classification) // No one would try 'c.Classification' here

この制限は、現在の安定版(EF core 5.0.7)よりも新しいEFのバージョンで変更される可能性があります。

フィルタリングできるもの(できないもの

というのは Where の拡張メソッドです。 IEnumerable であることから、コレクションのみがフィルタリング可能であることがわかります。参照ナビゲーションプロパティをフィルタリングすることはできません。注文を取得し、その Customer プロパティにのみ入力したい場合、顧客がアクティブであるときに Include :

context.Orders.Include(c => c.Customer.Where( ... // obviously doesn't compile

フィルタリングされたインクルードとクエリのフィルタリングの比較

フィルタリング Include は、クエリ全体のフィルタリングにどのような影響を与えるかについて、いくつかの混乱を生じさせました。経験則から言うと、それはありません。

ステートメントは...

context.Customers.Include(c => c.Orders.Where(o => !o.IsDeleted))

...戻り値 すべて の顧客を返します。のフィルタは Include のフィルターはメインクエリによって返されるアイテムの数には影響を与えません。

一方、ステートメント...

context.Customers
    .Where(c => c.Orders.Any(o => !o.IsDeleted))
    .Include(c => c.Orders)

...少なくとも1つの削除されていない注文を持つ顧客だけを返しますが、その顧客は すべて の注文を持つ顧客のみを返します。 Orders コレクションに含まれる注文のすべてです。によって返される顧客ごとの注文には、メインクエリのフィルターは影響しません。 Include .

削除されていない注文を持つ顧客を取得し、その削除されていない注文のみを読み込むこと。 両方とも フィルタが必要です。

context.Customers
    .Where(c => c.Orders.Any(o => !o.IsDeleted))
    .Include(c => c.Orders.Where(o => !o.IsDeleted))

フィルタリングされたインクルードとプロジェクション

もう一つの混乱する分野は、フィルタリングされた Include と投影 ( select new { ... } )は関連しています。単純なルールは、プロジェクションは Include を無視することです。以下のようなクエリ...

context.Customers
    .Include(c => c.Orders)
    .Select(c => new { c.Name, c.RegistrationDate })

に結合しないSQLを生成します。 Orders . EFに関しては...

context.Customers
    .Select(c => new { c.Name, c.RegistrationDate })

となると、混乱します。 Include がフィルタリングされているのに Orders は投影にも使われます。

context.Customers
    .Include(c => c.Orders.Where(o => !o.IsDeleted))
    .Select(c => new 
    { 
        c.Name, 
        c.RegistrationDate,
        OrderDates = c.Orders.Select(o => o.DateSent)
    })

ある人は OrderDates には削除されていないオーダーの日付だけが含まれていると思うかもしれませんが、それらはすべての Orders . ここでも、投影は完全に Include . 投影と Include は別世界の話です。

彼らがいかに厳密に自分の人生を歩んでいるかは、このクエリによっておもしろおかしく示されている。

context.Customers
    .Include(c => c.Orders.Where(o => !o.IsDeleted))
    .Select(c => new 
    { 
        Customer = c, 
        OrderDates = c.Orders.Select(o => o.DateSent)
    })

さて、ちょっと間を置いて、結果を予想してみましょう......。

それほど単純なルールではありません。 常に を無視することです。 Include . プロジェクション内に Include を適用できる実体がある場合、それは が適用される。ということは Customer はその削除されない Orders が含まれるのに対し OrderDates にはすべての日付が含まれています。正しく理解できましたか?