实体框架按排序顺序加载子集合

Posted

技术标签:

【中文标题】实体框架按排序顺序加载子集合【英文标题】:Entity Framework loading child collection with sort order 【发布时间】:2012-04-13 21:38:03 【问题描述】:

我有两个表,一个父表和一个子表。子表有一个列排序顺序(一个数值)。由于 EF 缺少对持久化 IList 的支持,包括排序顺序而不暴露排序顺序(请参阅:Entity Framework persisting child collection sort order),我的子类还有一个属性 SortOrder,以便我可以使用排序顺序存储子级。

与引用问题的作者相比,我尝试加载始终排序的子项。因此,如果我加载我期望的父实例,则子集合按排序顺序排序。如何使用 Code First Fluent API 和 POCO 实现此行为?

提示:不能对子集合调用 .Sort(...)。

【问题讨论】:

github.com/aspnet/EntityFrameworkCore/issues/15171 【参考方案1】:

您无法直接实现它,因为 EF 中的预先加载或延迟加载都不支持排序或过滤。

您的选择是:

从数据库加载数据后对应用程序中的数据进行排序 执行单独的查询以加载子记录。使用单独的查询后,您可以使用 OrderBy

第二个选项可以与显式加载一起使用:

var parent = context.Parents.First(...);
var entry = context.Entry(parent);
entry.Collection(e => e.Children)
     .Query()
     .OrderBy(c => c.SortOrder)
     .Load();

【讨论】:

别忘了补充:system.data.entity 和 system.linq 的 using 语句 另外不要忘记对父对象进行空检查 10 年后...看来您的答案对于 Entity Framework Core 仍然适用。这真的很可悲,因为 NHibernate 支持直接在映射中定义排序顺序。【参考方案2】:

你可以在一个查询中高效地做到这一点,语法很尴尬:

var groups = await db.Parents
    .Where(p => p.Id == id)
    .Select(p => new
        
            P = p,
            C = p.Children.OrderBy(c => c.SortIndex)
        )
    .ToArrayAsync();

// Query/db interaction is over, now grab what we wanted from what was fetched

var model = groups
    .Select(g => g.P)
    .FirstOrDefault();

说明

异步注释

我碰巧在这里使用了 async 扩展,您可能应该使用它,但如果您需要同步查询而不损害有效的子排序,则可以摆脱 await/async

第一块

默认情况下,从 Db 获取的所有 EF 对象都被“跟踪”。此外,EF 等效于 SQL Select 是围绕匿名对象设计的,您可以看到我们在上面选择了这些对象。创建匿名对象时,分配给PC 的对象都被跟踪,这意味着它们的关系被记录并且它们的状态由EF 更改跟踪器维护。由于CP 中的子列表,即使您没有在匿名对象中明确要求它们关联,EF 还是将它们作为这个子集合加载, 因为它在架构中看到的关系。

要了解更多信息,您可以将上述内容分成 2 个单独的查询,在完全不同的 Db 调用中仅加载父对象,然后仅加载子列表。 EF Change Tracker 会注意到并为您将子对象加载到父对象中。

第二块

我们欺骗了 EF 返回已订购的孩子。现在我们只抓取 Parent 对象 - 它的子对象仍将按照我们想要的顺序附加。

作为集合的 Null 和表

这里有一个尴尬的两步,主要用于围绕空值的最佳实践;它可以做两件事:

将数据库中的事物视为集合,直到可能的最后一刻。

避免空异常。

换句话说,最后一个块可能是:

var model = groups.First().P;

但如果该对象不存在于数据库中,则会因空引用异常而爆炸。 C# 6 will introduce another alternative though, the null property coalescence operator - 所以将来您可以将最后一个块替换为:

var model = groups.FirstOrDefault()?.P;

【讨论】:

这对我有用!多么棒的黑客!经过几个相关的问题,终于有了一个很好的答案! X181 应该将此标记为正确答案,IMO。 我收到此错误:The ObjectContext instance has been disposed and can no longer be used for operations that require a connection. 我在您的代码中所做的所有更改是将FirstOrDefault() 替换为ToList()。有什么想法吗? @JoSmo 听起来您在这个受限样本之外遇到了问题。您很可能将 DbContext 放置在某个地方,可能在另一个线程中,这导致此代码失败。您的问题可能最好作为 *** 上的新问题,而不是在 cmets 中的讨论 - 发布完整代码,至少包括任何 using 语句和线程。 @vtortola 添加了更多解释以尝试解释这里发生了什么,希望对您有所帮助。 有谁知道这是否适用于 EF7?它对我不起作用,我猜这就是原因。【参考方案3】:

除了需要订购,我还需要限制孩子的结果。我是这样做的:

var transactions = await _context.Transaction
            .Include(x => x.User)
            .OrderByDescending(x => x.CreatedAt)
            .Where(x => x.User.Id == _tenantInfo.UserId)
            .Take(10)
            .ToListAsync();

var viewmodel = _mapper.Map<UserViewModel>(transactions.First().User);

【讨论】:

以上是关于实体框架按排序顺序加载子集合的主要内容,如果未能解决你的问题,请参考以下文章

归并排序

实体框架 - 根据子集合获取父级

两个Excel有一列内容相同,这一列排列顺序不一样,怎么把这两个表内容合并成一张表?

停止加载子集合,如果我更新父集合

使用 nHibernate 选择具有许多子集合的实体的性能不佳

按插入顺序检索核心数据实体