实体框架按排序顺序加载子集合
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
是围绕匿名对象设计的,您可以看到我们在上面选择了这些对象。创建匿名对象时,分配给P
和C
的对象都被跟踪,这意味着它们的关系被记录并且它们的状态由EF 更改跟踪器维护。由于C
是P
中的子列表,即使您没有在匿名对象中明确要求它们关联,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有一列内容相同,这一列排列顺序不一样,怎么把这两个表内容合并成一张表?