用于从 LastOrDefault 父项中查找子项的实体框架 LINQ

Posted

技术标签:

【中文标题】用于从 LastOrDefault 父项中查找子项的实体框架 LINQ【英文标题】:Entity Framework LINQ for finding sub items from LastOrDefault parent 【发布时间】:2017-04-13 08:55:46 【问题描述】:

我的相关对象很少,关系就像

    public class Project

    public List<ProjectEdition> editions;


public class ProjectEdition

    public List<EditionItem> items;


public class EditionItem



我只想为每个项目从 ProjectEditions 的最后条目中获取 EditionItems

例子

Project#1 -> Edition#1 [包含少量版本项],Edition#2 [包含少量版本项]

项目#2 -> 版本#1 、版本#2 和版本#3

我需要的输出仅包含 Project#1 的 Edition#2 和 Project#2 的 Edition#3 中的 EditionItems 。我的意思是来自项目的最新版本或项目的最后版本的 EditionItems

为了得到这个,我尝试了这个查询

 List<EditionItem> master_list = context.Projects.Select(x => x.ProjectEditions.LastOrDefault())
                                      .SelectMany(x => x.EditionItems).ToList();

但它在 LatsOrDefault() 部分返回错误

EntityFramework.SqlServer.dll 中出现“System.NotSupportedException”类型的异常,但未在用户代码中处理

附加信息:LINQ to Entities 无法识别方法 '---------.Models.ProjectEdition LastOrDefault[ProjectEdition](System.Collections.Generic.IEnumerable`1

那么我如何过滤项目的最后一个版本,然后在单个 LINQ 调用中从中获取 EditionItems 列表

【问题讨论】:

倒序排列得到FirstOrDefault() 正如您的例外所说,您的提供商不支持LastOrDefault()。如果您考虑一下,当您使用 SQL 时,如果没有应用排序顺序,“last”这个术语就没有任何意义,因为记录可以来自服务器而没有特定的顺序。 那我怎样才能达到同样的效果呢?我正在使用 SQL Server 实体框架模型 您必须选择通过ProjectEdition 中的哪个属性来订购实体。假设那里有一个属性Id,那么你可以使用List&lt;EditionItem&gt; master_list = context.Projects.Select(x =&gt; x.ProjectEditions.OrderByDescending(pe =&gt; pe.Id).FirstOrDefault()).SelectMany(x =&gt; x.EditionItems).ToList(); @granit 确实如此。你应该把它作为一个答案。 【参考方案1】:

Granit 的答案是正确的,所以我不会重复他的代码。我想补充一下这种行为的原因。

实体框架很神奇(有时太神奇了),但它将您的 LINQ 查询转换为 SQL,并且您的底层数据库可以执行的操作存在限制(本例中为 SQL Server)。

当您调用context.Projects.FirstOrDefault() 时,它会被翻译成类似Select TOP 1 * from Projects 的东西。请注意TOP 1 部分 - 这是限制返回行数的 SQL Server 运算符。这是 SQL Server 中查询优化的一部分。 SQL Server 没有任何运算符可以为您提供LAST 1 - 因为它需要运行查询,返回所有结果,取最后一个并转储其余的 - 这不是很有效,想想一个有几个表(bi) 百万条记录。

因此,您需要对查询应用任何所需的排序顺序并限制返回的行数。如果您需要查询中的最后一条记录 - 应用反向排序顺序。您确实需要排序,因为 SQL Server does not guarantee order of records returned 如果没有 Order By 应用于查询 - 这是由于数据在内部存储的方式。

当您使用 EF 编写 LINQ 查询时,我建议您密切关注查询生成的 SQL - 有时您会看到它们的复杂程度,并且您可以轻松地简化查询。有时在启用延迟加载的情况下,您只需按一下键(字面意思)就会引入 N+1 问题。我使用ExpressProfiler 观看生成的 SQL,LinqPad 也可以显示 SQL 查询,还有其他工具。

【讨论】:

有用的解释!【参考方案2】:

您不能使用LastOrDefault()Last() 所讨论的方法here。

Insetad,您可以将OrderByDescending()FirstOrDefault() 结合使用,但首先您需要在ProjectEdition 中有一个属性,您要使用它来订购实体。例如。如果ProjectEdition 有一个属性Id(很有可能),您可以使用以下LINQ 查询:

List<EditionItem> master_list = context.Projects.Select(
                  x => x.ProjectEditions
                      .OrderByDescending(pe => pe.Id)
                      .FirstOrDefault())
                      .SelectMany(x => x.EditionItems).ToList();

【讨论】:

【参考方案3】:
List<EditionItem> master_list = context.Projects
                                .Select(p => p.editions.LastOrDefault())
                                .SelectMany(pe => pe.items).ToList();

如果 LastOrDefault 不支持,您可以尝试使用 OrderByDescending

List<EditionItem> master_list = context.Projects
              .Select(p => p.editions.OrderByDescending(e => e.somefield).FirstOrDefault())
              .SelectMany(pe => pe.items).ToList();

【讨论】:

您可以尝试使用 OrderByDescending, p.editions.OrderByDescending().FirstOrDefault() 吗? @JibinMathew,还要检查确保您的 context.Projects 返回 IEnumerable,因此请尝试使用 context.Projects.AsEnumerable().Select()。 用有用的 cmets 投票有帮助! @trailmax,非常感谢您的解释,我已经执行了它并发布了工作查询!一旦我注意到 LastOrDefault 不支持,我就为 Orderby 添加了评论,并在稍后更新了答案! thnx :) @MukeshModhvadiya 您可能会考虑删除 AsEnumerable 建议,因为将整个表加载到内存中然后执行 LINQ to Objects 查询是一个糟糕的主意,其中必须考虑延迟/急切加载等问题帐户。【参考方案4】:
from p in context.project
from e in p.projectEdition.LastOrDefault()
select new EditionItem

    item1 = e.item1

请试试这个

【讨论】:

以上是关于用于从 LastOrDefault 父项中查找子项的实体框架 LINQ的主要内容,如果未能解决你的问题,请参考以下文章

查找并替换xml文档中父项中子项的重新匹配匹配项

Hibernate - 删除子项时如何从父项中删除实体

使用 Composition API 在 Vue 中处理子项和父项中的提交事件

根据父项中的值按 Id 查找子文档

如何查找非间接/嵌套父项的子项

在JPA中删除子项时保持实体关系同步