简体   繁体   中英

Code First - Self-referencing one to many relation

I've found a lot of similar questions here, but none of them seems to help me with my problem. Fluent api & attributes didn't help. The database was created, but when adding an object to it, it crashed. I want to have a class that has a collection of itself. Here's the code I have:

[Table("UObjects")]
public class UObject
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Browsable(false)]
    public long ID { get; set; }
    public string Name { get; set; }
    [Browsable(false)]
    public long? ParentID { get; set; }

    public virtual UObject UParent { get; set; }
    [Browsable(false)]
    public virtual ICollection<UObject> UObjects { get; set; }
}


public class MyContext : DbContext
{
    public DbSet<UObject> UObjects { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // This fluent API didn't help
        //modelBuilder.Entity<UObject>()
        //        .HasOptional(u => u.UParent)
        //        .WithMany(u => u.UObjects)
        //        .HasForeignKey(u => u.ParentID);

        //modelBuilder.Entity<UObject>()
        //        .HasOptional(u => u.UParent)
        //        .WithMany(u => u.UObjects)
        //        .Map(c =>
        //        {
        //            c.MapKey("ParentID");
        //            c.ToTable("UObjects");
        //        });
    }
}

Records in database are like this:

ID | Name       | ParentID
------------------------------------
1  | First      | 0
2  | SubFirst   | 1
3  | SubSecond  | 1
4  | SubThird   | 2
5  | SubFourth  | 2

So how my object should look after loading the entities is next:

   - First
      - SubFirst
         - SubThird
         - SubFourth
      - SubSecond

But every object has an empty collection. What should I do to make it work properly?

You only need to mention the self reference by correcting on field rather than on navigating property like this:

 [ForeignKey("UParent")]    // EF need for self reference
 public long? ParentID { get; set; }

And in constructor, initialize navigation properties like this:

  public UObject()       
    {
        // this is necessary otherwise EF will throw null object reference error. You could also put ?? operator check for a more interactive solution.  
        UObjects = new List<UObject>(); 
    }

And also need to override as you were doing but like this:

   protected override void OnModelCreating(DbModelBuilder modelBuilder)     
    {   
        // folowwing is also necessary in case you're using identity model     
        base.OnModelCreating(modelBuilder);               
        modelBuilder.Entity<UObjects>()       
            .HasOptional<UObjects>(u => u.UParent) // EF'll load Parent if any     
            .WithMany(u => u.UObjects);        // load all childs if any 
    }

An entity class almost identical to yours works in EF Core. I renamed your property ParentID to UParentID and added constructors.

[Table("UObjects")]
public class UObject
{
  protected UObject()
  {
    UObjects = new List<UObject>();
  }

  public UObject(UObject parent, string name)
    : this()
  {
    Name = name;
    UParent = parent;
    UParent?.UObjects.Add(this);
  }

  [Key]
  [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  public long ID { get; set; }
  public string Name { get; set; }
  public long? UParentID { get; set; }

  public virtual UObject UParent { get; set; }
  public virtual ICollection<UObject> UObjects { get; set; }
}

In ApplicationDBContext I only have this:

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    builder.Entity<UObject>();
}

Usage (see how the properties of the root object are filled with correct values): 预加载

Note: I didn't bother about deletion in this code. If you need it, things will probably get more complicated.

  1. Decorate your UParent Property with ForeignKey Attribute and? since it can be nullable

[ForeignKey("ParentID")] public virtual UObject? UParent { get; set; }

  1. In database: Set ParentId value to 'NULL' if there is no parent.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM