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

时间:2012-03-30 07:50:34

标签: entity-framework ef-code-first poco code-first

我有两个表,父表和子表。子表具有列排序顺序(数值)。由于EF缺少支持IList包含排序顺序而不暴露排序顺序(参见:Entity Framework persisting child collection sort order),我的子类还有一个属性SortOrder,因此我可以使用排序顺序存储子项。

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

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

3 个答案:

答案 0 :(得分:38)

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

您的选择是:

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

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

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

答案 1 :(得分:27)

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

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对象都被跟踪。&#34;此外,EF&#39;等效于SQL Select是围绕匿名对象设计的,您可以在上面选择它们。创建匿名对象时,将跟踪分配给PC的对象,这意味着它们的关系会被记录,并且它们的状态由EF更改跟踪器维护。由于CP中的子项列表,即使您没有要求它们在您的匿名对象中明确相关, EF也会将它们作为此子集合加载,< / em>因为它在架构中看到的关系。

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

第二块

我们欺骗EF回归有序的孩子。现在我们只抓住Parent对象 - 它的子节点仍然按照我们想要的顺序连接。

Nulls and Tables as Sets

这里有一个尴尬的两步,主要是针对零点的最佳实践;它可以做两件事:

  • 将数据库中的内容视为设置,直到可能的最后一刻。

  • 避免空例外。

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

var model = groups.First().P;

但是如果对象没有出现在数据库中,则会以空引用异常爆炸。 C# 6 will introduce another alternative though, the null property coalescence operator - 所以将来你可以用:

替换最后一个块
var model = groups.FirstOrDefault()?.P;

答案 2 :(得分:0)

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

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