私はmoでEntity Frameworkについて学んでいますが、問題があります!!
DBから親とその子のサブセットを取得できないと考えるのが正しいかどうかを誰かが明確にできますか?
例えば...
db.Parents
.Include(p => p.Children)
.Where(p => p.Children.Any(c => c.Age >= 5))
これにより、5歳以上の子を持つすべての親が返されますが、Parents.Childrenコレクションを反復処理すると、すべての子が存在します(5歳以上の子だけでなく)。
これで、クエリは私には意味があります(子供を含めるように要求し、それらを取得しました!)が、いくつかのシナリオでwhere句を子コレクションに適用したいと想像できます。
質問:
いくつかのブログとSO件名に触れる投稿を見つけましたが、私の小さな脳に十分な説明をするものは何もありません。
[〜#〜] edit [〜#〜]
これを読んだこと ブログ (Daz Lewisに感謝).......まだわかりません!!!
ブログの例では、Parentの1つのインスタンスに対してどのようにそれを達成できるかを見ることができますが、コレクションを使用してそれを実行する方法を見つけるのに苦労しています。
各親が子のフィルター処理されたコレクション(年齢> = 5)を持つIEnumerableを取得するにはどうすればよいですか?
さらなる解明:
DonAndreのコメントに答えて、私はa)5歳以上の子供を持つ両親のリスト(そしてそれらの子供だけを含む)の後です。
任意の助けに感謝、
ありがとう。
単一のデータベースラウンドトリップでフィルター処理された子コレクションを持つ親のコレクションを取得する唯一の方法は、投影を使用することです。イーガーロード(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()
を使用して)Children
のParent
コレクションにも自動的に入力します。この場合、その後、匿名の結果タイプから親を投影できます(メモリ内で発生し、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();
_
以下の例を使用して、必要なことを行う必要があります。詳しくは こちら をご覧ください。
db.Entry(Parents)
.Collection("Children")
.Query().Cast<Child>()
.Where(c => c.Age >= 5))
.Load();
親と子は別々のエンティティとしてはあまり適していないと思います。子は常に親になることもでき、通常、子には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; }
}
グループをこのクラスのインスタンスに投影します。これにより、これらの結果を他のメソッドに渡す必要がある場合に簡単になります。