实体框架:查询子实体

发布于 2024-12-09 09:17:17 字数 379 浏览 0 评论 0原文

看来我无法从数据库中获取父级及其子级的子集。

例如...

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

这将返回所有有 5 岁以上孩子的“父母”,但如果我迭代“Parents.Children”集合,所有孩子都将出现(而不仅仅是 5 岁以上的孩子)。

现在这个查询对我来说确实有意义(我要求包含子项并且我已经得到了它们!),但可以想象我希望在某些情况下将 where 子句应用于子集合。

我怎样才能得到一个 IEnumerable,其中每个父母都有一个经过过滤的孩子集合(年龄> = 5)?

It seems that I can't get a parent and a subset of its children from the db.

For example...

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

This will return all Parents that have a child aged 5+, but if I iterate through the Parents.Children collection, all children will be present (not just those over 5 years old).

Now the query does make sense to me (I've asked to include children and I've got them!), but can imagine that I would like to have the where clause applied to the child collection in some scenarios.

How could I get an IEnumerable, in which each of the parents has a filtered collection of Children (Age>=5)?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

还在原地等你 2024-12-16 09:17:17

在单个数据库往返中获取具有已过滤子集合的父集合的唯一方法是使用投影。无法使用预先加载 (Include),因为它不支持过滤,Include 始终加载整个集合。 @Daz 显示的显式加载方式需要每个父实体进行一次往返。

示例:

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

您可以直接使用这个匿名类型对象的集合。 (您还可以投影到您自己的命名类型中,而不是匿名投影(但不能投影到像 Parent 这样的实体中)。)

EF 的上下文还将填充该对象的 Children 集合。如果您不禁用更改跟踪(例如使用 AsNoTracking()),则自动为 Parent。在这种情况下,您可以将父项投影到匿名结果类型之外(发生在内存中,没有数据库查询):

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

parents[i].Children 将包含每个 Parent< 的过滤子项。 /代码>。


编辑到问题中的最后一次编辑:

我正在寻找 a) 拥有 5 岁以上孩子的父母名单(以及
仅包括那些孩子)。

上面的代码将返回所有父母,并仅包含 Age >= 5 的孩子,因此如果只有 的孩子,则可能还具有空孩子集合的父母>年龄 < 5. 您可以使用父级的附加 Where 子句来过滤掉这些子句,以仅获取具有至少一个任何)子级的父级年龄 >= 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();

The only way to get a collection of parents with a filtered children collection in a single database roundtrip is using a projection. It is not possible to use eager loading (Include) because it doesn't support filtering, Include always loads the whole collection. The explicite loading way shown by @Daz requires one roundtrip per parent entity.

Example:

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

You can directly work with this collection of anonymous type objects. (You can also project into your own named type instead of an anonymous projection (but not into an entity like Parent).)

EF's context will also populate the Children collection of the Parent automatically if you don't disable change tracking (using AsNoTracking() for example). In this case you can then project the parent out of the anonymous result type (happens in memory, no DB query):

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

parents[i].Children will contain your filtered children for each Parent.


Edit to your last Edit in the question:

I am after a) A list of parents who have a child older than 5 (and
include only those children).

The code above would return all parents and include only the children with Age >= 5, so potentially also parents with an empty children collection if there are only children with Age < 5. You can filter these out using an additional Where clause for the parents to get only the parents which have at least one (Any) child with 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();
金兰素衣 2024-12-16 09:17:17

在 EF Core 5.0 中,Inclusion 方法现在支持筛选包含的实体。

https://learn.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();

In EF Core 5.0, the Include method now supports filtering of the entities included.

https://learn.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();
于我来说 2024-12-16 09:17:17

以您的示例为例,以下内容应该可以满足您的需要。看看此处了解更多信息。

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

Taking your example the following should do what you need. Take a look here for more info.

db.Entry(Parents)
.Collection("Children")
.Query().Cast<Child>()
.Where(c => c.Age >= 5))
.Load();
无风消散 2024-12-16 09:17:17

我认为父母和孩子并不适合作为独立的实体。孩子总是也可以是父母,通常孩子有两个父母(父亲和母亲),所以这不是最简单的上下文。但我假设您只有简单的 1:n 关系,如我使用的以下主从模型所示。

您需要做的是创建 左外连接< /a> (这个答案引导我走上了正确的道路)。这样的联接做起来有点棘手,但这里是代码

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 和 the 之间执行附加的 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; }
}

,然后将组投影到此类的实例中。如果您需要将这些结果传递给其他方法,这会变得更容易。

I think parents and child are not really well suited as separate entities. A child can always also be a parent and usually a child has two parents (a father and a mother), so it's not the simplest context. But I assume you just have a simple 1:n relationship as in the following master-slave model that I used.

What you need to do is make a left outer join (that answer has led me on the right path). Such a join is a bit tricky to do, but here's the code

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);
}

This will translate to the following SQL statement with EF 4.1

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)

Note that it is important to perform the additional where clause on the age on the joined collection and not between the from and the select.

EDIT:

IF you want a hierarchical result you can convert the flat list by performing a grouping:

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);
}

Note that I used an anonymous type to store the hierarchical result. You can of course create also a specific type like this

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

and then project the group into instances of this class. That makes it easier if you need to pass these results to other methods.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文