One to many recursive relationship with Code First

左心房为你撑大大i 提交于 2021-01-27 04:27:52

问题


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
}

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.

I have two issues:

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

and

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

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.

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!


回答1:


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

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

selector(node)

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

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

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

Preventing it

But why are these Children collections null?

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

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

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.

Flattening explained

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

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

Some remarks

  1. I would like to have the parent node on top of the collection, so I would change the Flatten method into

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



来源:https://stackoverflow.com/questions/27720369/one-to-many-recursive-relationship-with-code-first

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!