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:
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..
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!
To create Foreign Key, you need to use ForeignKey attribute with specifying the name of the property as parameter. You also need to specify the name of the table which is going to participate in relationship. I mean to say, define the foreign key table. Thanks for reading this article, hope you enjoyed it.
A relationship is recursive if the same entity type appears more than once. A typical business example is a rule such as “an employee supervises other employees”.
A relationship between two entities of a similar entity type is called a recursive relationship. Here the same entity type participates more than once in a relationship type with a different role for each instance. In other words, a relationship has always been between occurrences in two different entities.
You can create such a relationship by defining a third table, called a junction table, whose primary key consists of the foreign keys from both table A and table B.
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.
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.
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).
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)));
}
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...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With