在使用 EF 4.1 Code-First 的 Include 和/或 Select 方法时订购导航属性?

Posted

技术标签:

【中文标题】在使用 EF 4.1 Code-First 的 Include 和/或 Select 方法时订购导航属性?【英文标题】:Order navigation properties when using Include and/or Select methods with EF 4.1 Code-First? 【发布时间】:2011-11-23 06:43:26 【问题描述】:

这是此处解释的问题的第二步:EF 4.1 code-first: How to load related data (parent-child-grandchild)?。 在@Slauma的指导下,我用这种方法成功地检索到了数据:

var model = DbContext.SitePages
    .Where(p => p.ParentId == null && p.Level == 1)
    .OrderBy(p => p.Order) // ordering parent 
    .ToList();

foreach (var child in model)  // loading children
    DbContext.Entry(child)
        .Collection(t => t.Children)
        .Query()
        .OrderBy(t => t.Order) // ordering children
        .Load();

    foreach (var grand in child.Children)  // loading grandchildren
        DbContext.Entry(grand)
            .Collection(t => t.Children)
            .Query()
            .OrderBy(t => t.Order) // ordering grandchildren 
            .Load();
    

虽然这种方法有效,但它会向数据库发送许多查询,我正在寻找一种方法来在一个查询中完成所有这些操作。在@Slauma 的指导下(在上面链接的答案中进行了解释),我将查询更改为这个:

var model2 = DbContext.SitePages
    .Where(p => p.ParentId == null && p.Level == 1)
    .OrderBy(p => p.Order)
    .Include(p => p.Children // Children: how to order theme???
        .Select(c => c.Children) // Grandchildren: how to order them???
    ).ToList();

现在,我如何在选择子(和孙子)时对其进行排序(如上面的第一个代码示例所示)?

【问题讨论】:

看看这个问题:***.com/questions/4156949/…。您正在尝试做的事情称为“急切加载”,显然,您不能将OrderByInclude 一起使用。 是的,我知道急切加载,如果您查看第一个代码(由我自己创建),您会看到我在每个级别的每个对象上都使用了 foreach 语句(顶部为孩子,并且,孙子的孩子)与您准备好的链接中解释的相同。但这需要对数据库进行更多查询!我正在寻找一种在一个查询中完成所有操作的方法,而不是更多! 您能否急切地加载整个结构,然后在需要时在视图中进行排序?几乎没有理由在数据访问逻辑中泄露表示逻辑(排序)。 只有一个原因:避免多次循环。这里有很多 foreach 循环,我想避免它们。在查询数据库或视图时,它们之间没有任何区别,我们将拥有:foreach(foreach())。很抱歉,我无法解释更多): 但我认为最后,我必须这样做。通过Include(Select()) 选择所有这些,然后订购它们。再次感谢您的关注。 【参考方案1】:

你试过关注吗?

...
Select(from ch in c.children
       orderby ch.property desending
       select ch)
...

【讨论】:

不起作用!错误是 包含路径表达式必须引用在类型上定义的导航属性。对引用导航属性使用虚线路径,对集合导航属性使用 Select 运算符。参数名称:路径【参考方案2】:

不幸的是,急切加载 (Include) 不支持对加载的子集合进行任何过滤或排序。有三种选择可以实现您想要的:

通过显式排序加载多次往返数据库。这是您问题中的第一个代码 sn-p 。请注意,多次往返不一定是坏事,Include 和嵌套的Include can lead to huge multiplication of transfered data between database and client。

通过IncludeInclude(....Select(....)) 使用预加载,并在加载后对内存中的数据进行排序:

var model2 = DbContext.SitePages
    .Where(p => p.ParentId == null && p.Level == 1)
    .OrderBy(p => p.Order)
    .Include(p => p.Children.Select(c => c.Children))
    .ToList();

foreach (var parent in model2)

    parent.Children = parent.Children.OrderBy(c => c.Order).ToList();
    foreach (var child in parent.Children)
        child.Children = child.Children.OrderBy(cc => cc.Order).ToList();

使用投影:

var model2 = DbContext.SitePages
    .Where(p => p.ParentId == null && p.Level == 1)
    .OrderBy(p => p.Order)
    .Select(p => new
    
        Parent = p,
        Children = p.Children.OrderBy(c => c.Order)
            .Select(c => new
            
                Child = c,
                Children = c.Children.OrderBy(cc => cc.Order)
            )
    )
    .ToList() // don't remove that!
    .Select(a => a.Parent)
    .ToList();

这只是一次往返,如果您不禁用更改跟踪(请勿在此查询中使用.AsNoTracking()),则可以使用。此投影中的所有对象都必须加载到上下文中(第一个 ToList() 是必需的原因)并且上下文会将导航属性正确地绑定在一起(这是一个称为“关系跨度”的功能 )。

【讨论】:

天啊,特别感谢 Slauma!干得好 :D:D:D 那行得通,我跟踪它,只针对 db (^_^) 发送一个查询,特别感谢亲爱的。我只是将内部的Select 更改为.Select(c => new Parent = c, Children = c.Children. OrderBy(cc => cc.Order) )。再次感谢。 @ChrisMoschini:你确定ProxyCreationEnabled 很重要吗?我几乎从不使用更改跟踪代理,并且关系跨度仍然有效。 @Slauma 你是对的!我们的代码既禁用了代理,又不跟踪无法进行这种投影的快速只读查询;但是禁用代理就好了,只是 AsNoTracking() 把扳手扔进去了。 另外一件事,只要我正在测试这个 - 排序子列表似乎从 SQL Server 查询长列表要快得多,而不是未定义顺序,即使你对无序列表没问题作为最终结果。 UPD 信息:因为 EF Core 5.0 - 支持过滤和排序Filtered include

以上是关于在使用 EF 4.1 Code-First 的 Include 和/或 Select 方法时订购导航属性?的主要内容,如果未能解决你的问题,请参考以下文章

EF 4.1 Code-First 项目上的 MvcMiniProfiler 不分析 SQL

如何使用 EF Core Code-First 创建具有子类别的类别表? [复制]

如何使用 EF Code-First 插入/更新可更新视图

如何使用EF Core Code-First桥接自己的表

EF Code-First如何使用复合键从表中读取A.

EF Code-First 中的种子方法