web-dev-qa-db-ja.com

Entity Framework:子エンティティのクエリ

私はmoでEntity Frameworkについて学んでいますが、問題があります!!

DBから親とその子のサブセットを取得できないと考えるのが正しいかどうかを誰かが明確にできますか?

例えば...

db.Parents
.Include(p => p.Children)
.Where(p => p.Children.Any(c => c.Age >= 5))

これにより、5歳以上の子を持つすべての親が返されますが、Parents.Childrenコレクションを反復処理すると、すべての子が存在します(5歳以上の子だけでなく)。

これで、クエリは私には意味があります(子供を含めるように要求し、それらを取得しました!)が、いくつかのシナリオでwhere句を子コレクションに適用したいと想像できます。

質問:

  1. 私が言ったことは正しいですか?
  2. Dbを大量に呼び出すことなく、dbから親とサブセットのみを取得することは可能ですか?
  3. 調子はどうですか? (初めてではないでしょう)!!!!

いくつかのブログとSO件名に触れる投稿を見つけましたが、私の小さな脳に十分な説明をするものは何もありません。

[〜#〜] edit [〜#〜]

これを読んだこと ブログ (Daz Lewisに感謝).......まだわかりません!!!

ブログの例では、Parentの1つのインスタンスに対してどのようにそれを達成できるかを見ることができますが、コレクションを使用してそれを実行する方法を見つけるのに苦労しています。

各親が子のフィルター処理されたコレクション(年齢> = 5)を持つIEnumerableを取得するにはどうすればよいですか?

さらなる解明:

DonAndreのコメントに答えて、私はa)5歳以上の子供を持つ両親のリスト(そしてそれらの子供だけを含む)の後です。

任意の助けに感謝、

ありがとう。

44
ETFairfax

単一のデータベースラウンドトリップでフィルター処理された子コレクションを持つ親のコレクションを取得する唯一の方法は、投影を使用することです。イーガーロード(Include)を使用することはできません。フィルタリングをサポートしていないため、Includeは常にコレクション全体をロードします。 @Dazが示す明示的なロード方法では、親エンティティごとに1つのラウンドトリップが必要です。

例:

_var result = db.Parents
    .Select(p => new
    {
        Parent = p,
        Children = p.Children.Where(c => c.Age >= 5)
    })
    .ToList();
_

この匿名型オブジェクトのコレクションを直接操作できます。 (匿名投影の代わりに独自の名前付き型に投影することもできます(ただし、Parentなどのエンティティには投影できません)。)

EFのコンテキストは、変更の追跡を無効にしない場合(たとえば、AsNoTracking()を使用して)ChildrenParentコレクションにも自動的に入力します。この場合、その後、匿名の結果タイプから親を投影できます(メモリ内で発生し、DBクエリはありません):

_var parents = result.Select(a => a.Parent).ToList();
_

_parents[i].Children_には、各Parentのフィルタリングされた子が含まれます。


編集質問の最後の編集:

私はa)5歳以上の子供を持つ両親のリスト(およびそれらの子供のみを含む)。

上記のコードはall親を返し、Age> = 5の子のみを含むため、空の子コレクションを持つ親も潜在的に含まれます。 Age <5の子のみが存在する場合、親に対して追加のWhere句を使用してこれらをフィルタリングし、atを持つ親のみを取得できます。少なくとも1つのAny)子とAge> = 5:

_var result = db.Parents
    .Where(p => p.Children.Any(c => c.Age >= 5))
    .Select(p => new
    {
        Parent = p,
        Children = p.Children.Where(c => c.Age >= 5)
    })
    .ToList();
_
47
Slauma

以下の例を使用して、必要なことを行う必要があります。詳しくは こちら をご覧ください。

db.Entry(Parents)
.Collection("Children")
.Query().Cast<Child>()
.Where(c => c.Age >= 5))
.Load();
3
Darren Lewis

親と子は別々のエンティティとしてはあまり適していないと思います。子は常に親になることもでき、通常、子には2つの親(父と母)がいるため、最も単純なコンテキストではありません。しかし、私が使用した次のマスタースレーブモデルのように、単純な1:n関係があると仮定します。

あなたがする必要があるのは 左外部結合 を作ることです(その答えは私を正しい道に導いた)。このような結合はややややこしいですが、ここにコードがあります

var query = from m in ctx.Masters
            join s in ctx.Slaves
              on m.MasterId equals s.MasterId into masterSlaves
            from ms in masterSlaves.Where(x => x.Age > 5).DefaultIfEmpty()
            select new {
              Master = m,
              Slave = ms
            };

foreach (var item in query) {
  if (item.Slave == null) Console.WriteLine("{0} owns nobody.", item.Master.Name);
  else Console.WriteLine("{0} owns {1} at age {2}.", item.Master.Name, item.Slave.Name, item.Slave.Age);
}

これは、EF 4.1で次のSQLステートメントに変換されます

SELECT 
[Extent1].[MasterId] AS [MasterId], 
[Extent1].[Name] AS [Name], 
[Extent2].[SlaveId] AS [SlaveId], 
[Extent2].[MasterId] AS [MasterId1], 
[Extent2].[Name] AS [Name1], 
[Extent2].[Age] AS [Age]
FROM  [dbo].[Master] AS [Extent1]
LEFT OUTER JOIN [dbo].[Slave] AS [Extent2]
ON ([Extent1].[MasterId] = [Extent2].[MasterId]) AND ([Extent2].[Age] > 5)

Fromとselectの間ではなく、結合されたコレクションの年齢に追加のwhere句を実行することが重要であることに注意してください。

編集:

階層的な結果が必要な場合は、グループ化を実行してフラットリストを変換できます。

var hierarchical = from line in query
                   group line by line.Master into grouped
                   select new { Master = grouped.Key, Slaves = grouped.Select(x => x.Slave).Where(x => x != null) };

foreach (var elem in hierarchical) {
   Master master = elem.Master;
   Console.WriteLine("{0}:", master.Name);
   foreach (var s in elem.Slaves) // note that it says elem.Slaves not master.Slaves here!
     Console.WriteLine("{0} at {1}", s.Name, s.Age);
}

階層型の結果を保存するために匿名型を使用したことに注意してください。もちろん、このような特定のタイプも作成できます

class FilteredResult {
  public Master Master { get; set; }
  public IEnumerable<Slave> Slaves { get; set; }
}

グループをこのクラスのインスタンスに投影します。これにより、これらの結果を他のメソッドに渡す必要がある場合に簡単になります。

2
Andreas