实体框架:查询子实体

时间:2011-10-13 10:55:34

标签: entity-framework linq-to-entities entity-framework-4.1

我正在了解实体框架,并且遇到问题!!

有人可以澄清我是否正确地认为我无法从数据库中获取父母及其子集的一部分?

例如......

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

这将返回所有有5岁以上孩子的父母,但是如果我遍历Parents.Children集合,所有孩子都将在场(不仅仅是5岁以上的孩子)。

现在查询确实对我有意义(我已经要求包含孩子,我已经得到了它们!),但可以想象我想在某些情况下将where子句应用于子集合。 / p>

问题:

  1. 我所说的是正确的吗?
  2. 是否可以从数据库中获取父项和一个子集,而无需调用db?
  3. 我离开了标记吗? (不会是第一次)!!!!
  4. 我找到了一些关于这个主题的博客和SO帖子,但没有什么可以解释它对我的小脑子的影响。

    修改

    阅读此blog(感谢Daz Lewis).......我仍然没有得到它!!!

    在博客中给出的示例中,我可以看到如何针对单个Parent实例实现它,但我正在努力弄清楚如何使用集合来实现它。

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

    进一步澄清:

    在回答DonAndre的评论时,我正在追踪a)有5岁以上孩子的父母名单(仅包括那些孩子)。

    任何帮助表示赞赏,

    感谢。

4 个答案:

答案 0 :(得分:46)

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

示例:

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

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

如果您不禁用更改跟踪(例如,使用Children),

EF的上下文也会自动填充Parent AsNoTracking()集合。在这种情况下,您可以将父项从匿名结果类型中调出(在内存中发生,无数据库查询):

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

parents[i].Children将包含每个Parent的过滤后的子项。


编辑到问题的最后一次修改:

  

我正在追踪a)有一个5岁以上孩子的父母名单(和   只包括那些孩子。)

上面的代码会返回所有父母,并且只包含Age> = 5的孩子,所以如果只有{{{}的孩子,那么父母也可能会有空子集合1}}< 5.您可以使用额外的Age条款对父母进行过滤,以便父母只能获得至少一个Where)子女Any的父母> = = 5:

Age

答案 1 :(得分:3)

举个例子,以下内容可以满足您的需求。看一下here了解更多信息。

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

答案 2 :(得分: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 :(得分:0)

在 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();