与Code First一对多递归关系 [英] One to many recursive relationship with Code First

查看:53
本文介绍了与Code First一对多递归关系的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用EF 6.1.2 Code First实现一个简单的自引用关系.

I am trying to implement a simple self referencing relationship with 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
}

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

In my application I have exactly one root branch. And except for this single root branch, every branch has exactly one parent (the parentId of the root branch is NULL). Other than that, every branch can have [0..n] subbranches.

我有两个问题:

  1. 我是否需要在OnModelCreating(DbModelBuilder modelBuilder)中指定任何额外的FluentApi代码,以使EF了解这种一对多的自引用关系?我尝试了这个: modelBuilder.Entity< Branch>().HasOptional< Branch>(b => b.Parent).WithMany(b => b.Children).); 但我不确定是否完全需要这个.
  2. 对于给定的分支,我想检索所有子级(一直向下到层次结构).到目前为止,这是我想出的:
  1. Do I need to specify any extra FluentApi code in OnModelCreating(DbModelBuilder modelBuilder) in order to make EF understand this one-to-many self-referencing relationship? I tried this: modelBuilder.Entity<Branch>().HasOptional<Branch>(b => b.Parent).WithMany(b => b.Children).HasForeignKey(b => b.ParentId); But I am not sure if I need this at all.
  2. For a given branch I want to retrieve all children (all the way down the hierarchy). This is what I came up with so far:

.

 public IEnumerable<Branch> GetBranches(Branch anyBranch)
 {
     return anyBranch.Flatten(b => b.Children);
 }

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

第二个片段不是我的.我在StackOverflow上的其他地方找到了它.老实说,我几乎不了解它应该如何工作.

The second snippet is not from me. I found it somewhere else on StackOverflow. To be honest, I hardly understand how it is supposed to work.

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

When I run my application and call GetBranches() (I tried this with several different branches), I receive an exception inside the Flatten() method. The error message says: "Value cannot be null. Parameter name: source". Unfortunately this does not give me any clue what is going wrong here.

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

I hope anybody can help me out here? Thanks so much!

推荐答案

异常原因

该异常是由 null 集合上的 Select SelectMany 引起的,在您的情况下,该结果是

Cause of the exception

The exception is caused by a Select or SelectMany on a null collection, in your case the result of

b => b.Children

对于层次结构中的每个分支,当 Children 集合到达该部分时,都将对其进行访问

For each branch in the hierarchy the Children collection is accessed when they reach the part

selector(node)

选择器是lambda表达式 b =>b.Children ,与方法相同

The selector is the lambda expression b => b.Children, which is the same as a method

IEnumerable<Branch> anonymousMethod(Branch b)
{
    return b.Children;
}

因此实际发生的是 b.Children.SelectMany(...) null.SelectMany(...),这会引发您所看到的异常.

So what actually happens is b.Children.SelectMany(...), or null.SelectMany(...), which raises the exception you see.

但是为什么这些 Children 集合为空?

But why are these Children collections null?

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

This is because lazy loading does not happen. To enable lazy loading the collection must be virtual:

public virtual ICollection<Branch> Children { get; set; }

当EF从数据库中获取一个 Branch 对象时,它会创建一个 proxy 对象,该对象是从 Branch 派生的对象,该对象将通过以下方式覆盖虚拟属性:可以延迟加载的代码.现在,当寻址到 b.Children 时,EF将执行一个查询以填充集合.如果没有子代,则集合将为空,而不是null.

When EF fetches a Branch object from the database it creates a proxy object, an object derived from Branch, that overrides virtual properties by code that is capable of lazy loading. Now when b.Children is addressed, EF will execute a query that populates the collection. If there are no children, the collection will be empty, not null.

因此,在 Flatten 方法中发生的事情是,首先获取分支的子代( selector(node)),然后依次获取这些子代中的每个子代(> SelectMany )再次调用 Flatten 方法(现在就像是 Flatten(x,选择器)方法,而不是扩展方法一样).

So what happens in the Flatten method is that first the children of the branch are fetched (selector(node)), subsequently on each of these children (SelectMany) the Flatten method is called again (now just as a method Flatten(x, selector), not an extension method).

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

In the Flatten method each node is added to the collection of its children (.Concat(new[] { node }), so in the end, all nodes in the hierarchy are returned (because Flatten returns the node that enters it).

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

  • 通过延迟加载获取层次结构效率很低.实际上,LINQ不是最适合查询层次结构的工具.要高效地执行此操作,将需要在数据库中使用CTE(公用表表达式)的视图.但这是一个不同的故事...

  • Fetching a hierarchy by lazy loading is quite inefficient. In fact, LINQ is not the most suitable tool for querying hierarchies. Doing this efficiently would require a view in the database that uses a CTE (common table expression). But that's a different story...

    这篇关于与Code First一对多递归关系的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆