与Code First的一对多递归关系

Ing*_*mar 7 entity-framework ef-code-first entity-framework-6

我试图用EF 6.1.2 Code First实现一个简单的自引用关系.

public class Branch 
{
    [Key]
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    public int? ParentId { get; set; }

    [ForeignKey("ParentId")]
    public virtual Branch Parent { get; set; }

    public ICollection<Branch> Children { get; set; } // direct successors
}
Run Code Online (Sandbox Code Playgroud)

在我的应用程序中,我只有一个根分支.除了这个单根分支外,每个分支只有一个父分支(根分支的parentId为NULL).除此之外,每个分支都可以有[0..n]个子分支.

我有两个问题:

  1. 我是否需要在OnModelCreating(DbModelBuilder模型构建器)中指定任何额外的FluentApi代码,以使EF了解这种一对多的自引用关系?我试过这个:modelBuilder.Entity<Branch>().HasOptional<Branch>(b => b.Parent).WithMany(b => b.Children).HasForeignKey(b => b.ParentId);但我不确定我是否需要这个.
  2. 对于给定的分支,我想要检索所有子级(一直到层次结构).这是我到目前为止提出的:

.

 public IEnumerable<Branch> GetBranches(Branch anyBranch)
 {
     return anyBranch.Flatten(b => b.Children);
 }
Run Code Online (Sandbox Code Playgroud)

 public static IEnumerable<T> Flatten<T>(this T node, Func<T, IEnumerable<T>> selector)
 {
     return selector(node).SelectMany(x => Flatten(x, selector))
                            .Concat(new[] { node });
 }
Run Code Online (Sandbox Code Playgroud)

第二个片段不是来自我.我发现它在StackOverflow上的其他地方.说实话,我几乎不明白它应该如何工作.

当我运行我的应用程序并调用GetBranches()(我尝试使用几个不同的分支)时,我在Flatten()方法中收到一个异常.错误消息显示:"值不能为null.参数名称:source".不幸的是,这并没有让我知道这里出了什么问题.

我希望有人可以帮助我吗?非常感谢!

Ger*_*old 10

异常的原因

该异常是由一个SelectSelectMany一个null集合引起的,在您的情况下是一个集合的结果

b => b.Children
Run Code Online (Sandbox Code Playgroud)

对于层次结构中的每个分支,Children集合在到达零件时被访问

selector(node)
Run Code Online (Sandbox Code Playgroud)

selector是λ表达式b => b.Children,其是相同的方法

IEnumerable<Branch> anonymousMethod(Branch b)
{
    return b.Children;
}
Run Code Online (Sandbox Code Playgroud)

那么实际发生的是b.Children.SelectMany(...),或者null.SelectMany(...),这会引发您看到的异常.

防止它

但为什么这些Children收藏品无效?

这是因为延迟加载不会发生.要启用延迟加载,集合必须是virtual:

public virtual ICollection<Branch> Children { get; set; }
Run Code Online (Sandbox Code Playgroud)

当EF Branch从数据库中获取对象时,它会创建一个proxy对象,一个派生自的对象Branch,它通过能够延迟加载的代码覆盖虚拟属性.现在,当b.Children解决时,EF将执行填充集合的查询.如果没有子节点,则集合将为空,不为空.

扁平化解释

所以在该Flatten方法中发生的事情是首先获取分支的子节点(selector(node)),随后在每个子节点上(SelectMany)Flatten再次调用该方法(现在仅作为方法Flatten(x, selector),而不是扩展方法).

在该Flatten方法中,每个节点都被添加到其子节点的集合中(.Concat(new[] { node })最终,返回层次结构中的所有节点(因为Flatten返回进入它的节点).

一些评论

  1. 我想将父节点放在集合的顶部,所以我将Flatten方法更改为

    public static IEnumerable<T> Flatten<T>(this T node, Func<T,IEnumerable<T>> selector)
    {
        return new[] { node }
            .Concat(selector(node).SelectMany(x => Flatten(x, selector)));
    }    
    
    Run Code Online (Sandbox Code Playgroud)
  2. 通过延迟加载获取层次结构是非常低效的.实际上,LINQ不是最适合查询层次结构的工具.有效地执行此操作将需要数据库中使用CTE(公用表表达式)的视图.但那是一个不同的故事......

  • 一切都按照您的描述完美运行。我无法想象我忘记了“virtual”关键字,因为我知道它对延迟加载的影响。但是你解释的方式 - 以及你必须投入的工作量:我真的不知道该说什么。太感谢了。我希望能给你 100 个积分或者其他什么;-) 祝你新年快乐。再次感谢!! (2认同)