实体框架:查询子实体 [重复]
Posted
技术标签:
【中文标题】实体框架:查询子实体 [重复]【英文标题】:Entity Framework: Querying Child Entities [duplicate] 【发布时间】:2011-12-06 20:55:07 【问题描述】:似乎我无法从数据库中获取父级及其子级的子集。
例如...
db.Parents
.Include(p => p.Children)
.Where(p => p.Children.Any(c => c.Age >= 5))
这将返回所有有 5 岁以上孩子的父母,但如果我遍历 Parents.Children 集合,所有孩子都会在场(不仅仅是 5 岁以上的孩子)。
现在这个查询对我来说确实有意义(我已经要求包含孩子并且我已经得到了他们!),但可以想象我希望在某些情况下将 where 子句应用于子集合。
我如何获得一个 IEnumerable,其中每个父母都有一个过滤后的儿童 (Age>=5) 集合?
【问题讨论】:
你想要的有两种可能性:a) 你想要一个有 5 岁以上孩子的父母的列表(包括那个孩子)或 b) 你想要一个 all 父母和他们的孩子的列表,要么是空的,要么只包含 5 岁以上的孩子。起初我以为你想要 a),现在我认为你想要 b)。请澄清 我已经编辑了我的答案,以考虑您对问题的最后澄清。 仅从 EF 核心 5 开始才有可能:***.com/q/43618096/861716。 【参考方案1】:以您的示例为例,以下内容应该可以满足您的需求。查看here 了解更多信息。
db.Entry(Parents)
.Collection("Children")
.Query().Cast<Child>()
.Where(c => c.Age >= 5))
.Load();
【讨论】:
感谢您的回复,但我还是很困惑!!!我已经编辑了我原来的问题来解释原因! 为 .Query() 方法添加什么命名空间,在System.Data.Entity.Infrastructure
中找不到它【参考方案2】:
我认为父母和孩子不太适合作为独立的实体。孩子也可以是父母,通常孩子有两个父母(父亲和母亲),所以这不是最简单的上下文。但我假设你只是有一个简单的 1:n 关系,就像我使用的以下主从模型一样。
您需要做的是创建left outer join(该答案使我走上了正确的道路)。这样的连接有点棘手,但这里是代码
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 之间。
编辑:
如果您想要分层结果,您可以通过执行分组来转换平面列表:
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;
然后将该组投影到此类的实例中。如果您需要将这些结果传递给其他方法,这会更容易。
【讨论】:
【参考方案3】:在单个数据库往返中获取具有过滤子集的父集的唯一方法是使用投影。无法使用急切加载 (Include
),因为它不支持过滤,Include
总是加载整个集合。 @Daz 显示的显式加载方式需要每个父实体往返一次。
例子:
var result = db.Parents
.Select(p => new
Parent = p,
Children = p.Children.Where(c => c.Age >= 5)
)
.ToList();
您可以直接使用这个匿名类型对象的集合。 (您也可以投影到您自己的命名类型而不是匿名投影(但不能投影到像Parent
这样的实体。)
如果您不禁用更改跟踪(例如使用AsNoTracking()
),EF 的上下文还将自动填充Parent
的Children
集合。在这种情况下,您可以将父级从匿名结果类型中投影出来(发生在内存中,没有数据库查询):
var parents = result.Select(a => a.Parent).ToList();
parents[i].Children
将包含每个Parent
的过滤子代。
编辑到您在问题中的最后一次编辑:
我在寻找 a) 有 5 岁以上孩子的父母名单(以及 只包括那些孩子)。
上面的代码将返回所有个父母,并且只包括Age
>= 5 的孩子,因此如果只有Age
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();
【讨论】:
当我使用公开 iqueryable 的存储库并且我无权访问上下文来执行过滤连接时,此方法非常适合我。 并使用 Func 不要重复自己...Func<Child, bool> filterChildren = (c) => c.Age >= 5;
如果你想在孩子身上加载虚拟属性怎么办......知道怎么做吗?【参考方案4】:
在 EF Core 5.0 中,Include 方法现在支持过滤所包含的实体。
https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#filtered-include
var data = db.Parents
.Include(p => p.Children.Where(c => c.Age >= 5))
.ToList();
【讨论】:
以上是关于实体框架:查询子实体 [重复]的主要内容,如果未能解决你的问题,请参考以下文章