0

I am using the following Tree class:

public class Tree
{
     public Tree() {}
        
     public Guid TreeRootNodeId { get; set; }
     public virtual Node TreeRootNode { get; set; }
     public virtual List<Node> Nodes { get; set; }
}

with the Node implementation:

public class Node
    {
        public Node() {}

        public Guid TreeId { get; set; }
        public virtual Tree Tree { get; set; }
        public Guid? LeftNodeId { get; set; }
        public virtual Node LeftNode { get; set; }
        public Guid? RightNodeId { get; set; }
        public virtual Node RightNode { get; set; }
    }

To setup up Entity Framework Core (EF) I used:

modelBuilder
  .Entity<Tree>()
  .HasMany(e => e.Nodes)
  .WithOne(e => e.Tree);

modelBuilder
  .Entity<Tree>()
  .HasOne<Node>(e => e.TreeRootNode)
  .WithOne(e => e.Tree)
  .HasForeignKey<Tree>(e => e.TreeRootNodeId);

modelBuilder
  .Entity<Node>()
  .HasOne(e => e.LeftNode)
  .WithMany();
            
modelBuilder
  .Entity<Node>()
  .HasOne(e => e.RightNode)
  .WithMany();

For performance reasons I want to store the TreeRootNode for each Tree to not always need to query all Node objects to access the root.

The issue: when I run the migrations EF the following exception is thrown:

Cannot create a relationship between 'Node.Tree' and 'Tree.TreeRootNode', because there already is a relationship between 'Tree.Nodes' and 'Node.Tree'. Navigation properties can only participate in a single relationship.

I added a new foreing key TreeRootNodeId to the Tree object. Looking at it from a database level I don't see any issue in having a FK point to a specific Node which is my root Node. But it seems EF has limitations.

Is there a way to configure EF to allow that design? If not is there a performant work around?

I already added a boolean to the Node which indicates if the Node IsRoot. But this is causing performance issues in the long run.

Thanks for your help!

2
  • 1
    Why do you need a Tree reference in Node class? Commented May 23, 2020 at 15:52
  • From any (leaf) Node I want very quickly access to the root without traversing the Tree (possibly using a recursive query). So my plan is to go from any Node to the Tree and from there directly the root. Commented May 23, 2020 at 16:14

1 Answer 1

1

First of all you should add properties for keys (Tree and Node classes).

Something like this:

[Key]
public Guid Id { get; set; }

The problem is that you cannot reference to Tree property in Node class multiple times. When you set both columns, EF Core cannot identify which property should be used to fill Tree property (leaf or root).

You should add another property for Tree instance. Node class:

public virtual Tree RootOf { get; set; }

and then change model creating code:

modelBuilder
  .Entity<Tree>()
  .HasOne<Node>(e => e.TreeRootNode)
  // it was: .WithOne(e => e.Tree)
  .WithOne(e => e.RootOf)
  .HasForeignKey<Tree>(e => e.TreeRootNodeId);

Property TreeId of Node class becomes now nullable. After it you will have 2 types on nodes in DB:

  • with TreeId (it is leaf)
  • null TreeId (root of some tree)

Also I recommend add property RootOfId to node to see the root of which tree it is.

UPDATE

I paste the final classes:

public class Tree
{
    [Key]
    public Guid Id { get; set; }
    public Guid TreeRootNodeId { get; set; }

    [ForeignKey(nameof(TreeRootNodeId))]
    public virtual Node TreeRootNode { get; set; }
    public virtual List<Node> Nodes { get; set; }
}

public class Node
{
    [Key]
    public Guid Id { get; set; }
    public Guid? TreeId { get; set; }
    public Guid? LeftNodeId { get; set; }
    public Guid? RightNodeId { get; set; }

    [ForeignKey(nameof(LeftNodeId))]
    public virtual Node LeftNode { get; set; }
    [ForeignKey(nameof(RightNodeId))]
    public virtual Node RightNode { get; set; }

    [ForeignKey(nameof(TreeId))]
    public virtual Tree Tree { get; set; }
    public virtual Tree RootOf { get; set; }
}

And OnModelCreating method:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<Tree>()
        .HasMany(x => x.Nodes)
        .WithOne(x => x.Tree);
}
Sign up to request clarification or add additional context in comments.

1 Comment

RootOf can be omited if you don't need it. The main trick is to make TreeId nullable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.