【问题标题】:How do I load a recursive entity without also loading the children at root level?如何加载递归实体而不在根级别加载子实体?
【发布时间】:2018-05-30 20:45:28
【问题描述】:

如果您查看CoreUI live demo website,您会在左侧看到一个导航栏,其中包含多个可折叠级别。我想实现这样的东西,但是是动态的。我创建了一个简单的类:

public class NavItem
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int navItemId { get; set; }
    public int sortOrder { get; set; }
    public bool isTitle { get; set; }
    public bool isDivider { get; set; }
    public string cssClass { get; set; }
    public string name { get; set; }
    public string url { get; set; }
    public string icon { get; set; }
    public string variant { get; set; }
    public string badgeText { get; set; }
    public string badgeVariant { get; set; }
    public virtual ICollection<NavItem> children { get; set; }
}

注意ICollection&lt;NavItem&gt; children 属性。

无论如何,我用一个示例数据集(CoreUI 示例)填充了它,它正确地保存在数据库中,其中一个名为 NavItemId1 的字段存储了任何孩子的父母的 ID。到目前为止一切顺利。

现在我想查询它,所以我做了显而易见的事情:

var nI = db.navItems.ToList();

这相当出色,生成了一个包含所有导航项的列表,children 属性在需要时正确填充了子项。

但是,它还包括根级别的所有子项...所以我有 40 个根级别项,而不是 15 个根级别项。

是否有我可以运行的 linq 查询来防止列表的根级别被子项填满(即排除任何字段 NavItemId1 != null),但仍能正确加载结构的其余部分?

例如这就是我现在得到的:

  • root1
  • root2
    • child1-of-root2
    • child2-of-root2
      • child1-of-child2-of-root2
  • root3
    • child1-of-root3&lt;-- I want my list to end here
  • child1-of-root2
  • child2-of-root2
    • child1-of-child2-of-root2
  • child1-of-child2-of-root2
  • child1-of-root3

我可以添加一个 isRoot 布尔属性,然后运行读取后查询以删除根级别没有设置 isRoot 的任何项目,例如

var nI = db.navItems.ToList();
nI = nI.Where(p => p.isRoot).ToList();

但这看起来很笨重。

显示问题的示例代码。特别注意第 6 项和第 16 项(第 1 项有 1 级儿童,第 2 项有 2 级)。

https://github.com/adev73/EFCore-LoadParentWithChildren-Example

【问题讨论】:

  • 不要编辑您的问题以包含答案。将答案标记为已接受(如果适合您),或者发布您自己可以接受的答案
  • 对不起...但我不想将自己的答案发布在其他人的答案之上。无论如何...它不起作用,所以我已经删除了它。

标签: c# linq entity-framework-core


【解决方案1】:

你得到的结果是正确的。您将所有 navItems 请求为一个列表,然后您将获得一个包含所有 navItems 的列表。子节点的修复是 EF 为您提供的一个不错的“奖励”。

您还正确地将过滤器应用于实例化列表而不是直接应用于数据集。如果您将其应用于数据集,您的子节点将不会被填充!然后,要填充子节点,您必须使用包含,但这只会让您获得 1 级,这可能还不够。当然您可以添加多个包含,但这不会很好地扩展...

你的“笨拙”的做事方式似乎并不那么笨拙……我觉得它相当优雅。

【讨论】:

  • 实际上,上面的两个答案是正确的:如果您包含“父”属性,则添加 .Where(p => p.parentId == null),它确实有效并产生正确的父/子集合。我只是很笨,没有看正确的父项!
  • 不,等等,我收回了。我只是再次运行它并没有加载孩子....
  • 我同意其他 2 个答案,即显式密钥更易于使用和过滤。只是为了确保:在尝试使用 parent-key == null 进行查询之前,您没有选择同一上下文中的任何项目?这将导致对新请求的查询进行自动修复。据我所知并尝试了几次,如果您没有明确表示想要它们,EF 核心不会自动加载关系。我对您的 linq 查询生成的 SQL 非常感兴趣
  • 是的,一旦你在 root 上添加了过滤,你需要一个 .Include 在孩子上加载层次结构,但是一旦使用它应该加载整个层次结构,请参阅:stackoverflow.com/a/41837737/84206
  • 我不确定如何获取它正在执行的底层 SQL(如果有帮助的话,我正在使用 sqlite)。如果您愿意,我可以将代码放入准系统项目中并分享它.. 给我 15 分钟...
【解决方案2】:

导航属性始终具有支持外键。您可以显式声明外键并在查询中利用它们。如果您将父外键添加到模型类,那么您可以在ParentKey == null 上过滤以仅获取根项目。您没有添加新列,因为该列已经以某种形式存在,您只需将其显式包含在模型中,以便可以在查询中使用。父键的名称以及将其添加到模型时的名称取决于您的映射。关于映射这些类型的键有很多问题。

这里是一个类似的例子,注意模型中声明的ParentId:Ordering a self-referencing hierarchy in LINQ

我总是在模型中包含所有外键,因为它对于某些类型的查询或更新操作很方便,在这些操作中,设置 FK 比查询相关对象来创建关系更有效。它还确保您的 FK 列不会被 EF 奇怪地命名。

【讨论】:

  • 如果我在 FK == null 上过滤它,则不会加载任何子级。 (我假设你的意思是var nI = db.navItems.Where(p =&gt; p.fk == null).ToList()
【解决方案3】:

我可能错了,但是,如果你有孩子,你不需要父母吗?

public class NavItem
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int navItemId { get; set; }
    public int? parentNavItemId { get; set; }
    public int sortOrder { get; set; }
    public bool isTitle { get; set; }
    public bool isDivider { get; set; }
    public string cssClass { get; set; }
    public string name { get; set; }
    public string url { get; set; }
    public string icon { get; set; }
    public string variant { get; set; }
    public string badgeText { get; set; }
    public string badgeVariant { get; set; }

    public NavItem parentNavItem { get; set; }
    public virtual ICollection<NavItem> children { get; set; }
}

然后你会过滤那些没有父母的人:

var items = db.NavItems
    .ToList() // get every item so that relations load correctly
    .Where(x => x.parentNavItemId == null) // then filter by top-level
    .ToList();

【讨论】:

  • 您需要数据库中的父级,但 EF 会为您做到这一点(字段名称有点奇怪,但现在这并不重要)。但是,对于包含其他类(甚至是同一类,即递归)的集合的类,不 - 你不需要父母......除非你需要从不是根的起点向上导航树 -这不是我需要做的事情。
  • 关于 c# 命名约定.... 我想您主要指的是我使用 camelCase 而不是 PascalCase?抱歉...我更喜欢 camelCase 属性,PascalCase 类型/类。事实上,我非常喜欢它,我在 Visual Studio 2017 中调整了规则,以停止纠缠我更改为 PascalCase...
  • @Ade EF 会为您执行此操作,但您无法对此进行过滤,并且总是最好是明确的。我在你的结构上没有看到另一种说法“这是根”,这似乎是你的问题
  • 似乎必须使 FK 可用(如您所述),或者必须使附加的“isRoot”或类似属性可用。可见的 FK 更好,因为它几乎可以保证是准确的。
  • @Ade 那我看不出我的回答有什么问题...?我可以在内存数据库上运行它,看看它是否正确加载了项目。但是isRoot 列会很奇怪
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多