实体框架:查询子实体
我正在学习实体框架,并且遇到问题!
有人可以澄清,如果我认为我无法从数据库中获取父项及其子项的子集,那么是否正确?
例如...
db.Parents
.Include(p => p.Children)
.Where(p => p.Children.Any(c => c.Age >= 5))
这将返回所有有5岁以上孩子的父母,但是如果我通过Parents.Children收集进行迭代,所有孩子都会在场(不只是5岁以上的孩子)。
现在查询确实对我有意义(我已经要求包括孩子,我已经拥有了它们),但是可以想象,我希望在某些场景中将where子句应用于子集合。
问题:
我发现了一些关于这个主题的博客和SO帖子,但没有任何东西可以解释为什么我的小脑袋足够好。
编辑
读过这个博客(谢谢达斯刘易斯).......我还是不明白!
在博客中给出的示例中,我可以看到如何通过一个Parent实例实现它,但我正在努力研究如何使用集合实现它。
我怎样才能得到一个IEnumerable,其中每个父母都有一个筛选的儿童(年龄> = 5)集合?
进一步澄清:
在回答DonAndre的评论时,我在a)一个有5岁以上孩子(并且只包括那些孩子)的父母名单。
任何帮助感激,
谢谢。
在单次数据库往返中获取带过滤子集合的父母的唯一方法是使用投影。 由于不支持筛选,因此不可能使用预先加载( Include
), Include
始终加载整个集合。 @Daz显示的明确加载方式要求每个父实体一次往返。
例:
var result = db.Parents
.Select(p => new
{
Parent = p,
Children = p.Children.Where(c => c.Age >= 5)
})
.ToList();
您可以直接使用这个匿名类型对象集合。 (您也可以投影到您自己的命名类型中,而不是匿名投影(但不是像Parent
这样的实体)。)
如果您AsNoTracking()
用更改跟踪(例如,使用AsNoTracking()
,则EF的上下文还会自动填充Parent
的Children
集合。 在这种情况下,您可以将父项从匿名结果类型中移出(发生在内存中,没有数据库查询):
var parents = result.Select(a => a.Parent).ToList();
parents[i].Children
孩子将为每个Parent
包含您的过滤孩子。
编辑到您的最后编辑问题:
我在a)一个有5岁以上孩子的父母名单(并且只包括那些孩子)。
上面的代码会返回所有父母,并且只包含Age
> = 5的孩子,如果只有Age
小于5岁的孩子,可能还会有一个空子女收藏的父母。您可以使用父母的附加Where
子句将其过滤掉只得到至少有一个( 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();
我认为父母和孩子并不适合作为独立的实体。 一个孩子总是也可以是父母,通常孩子有两个父母(父亲和母亲),所以这不是最简单的背景。 但是我假设你只是有一个简单的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)
请注意,在连接的集合上执行附加的where子句并非在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; }
}
然后将该组投影到此类的实例中。 如果你需要将这些结果传递给其他方法,这会更容易。
链接地址: http://www.djcxy.com/p/37649.html