8

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?

GaaRa
  • 520
  • 6
  • 21
  • check this http://stackoverflow.com/questions/10421351/many-to-many-relationship-between-entities-of-same-type-in-mvc3/10422172#10422172 – Shyju Oct 20 '12 at 14:50
  • tried this already. It created the database but it crashes when adding new object and calling SaveChanges() – GaaRa Oct 20 '12 at 15:07
  • 5
    "*it crashed*" isn't really a good problem description. 1) What is the exception exactly? 2) What query did you run when you loaded the entities with empty collections? 3) Why do you have a `ParentID` of `0` in the first database row? It violates a referential constraint (there is no row with `ID` 0). Or do you mean `NULL`? – Slauma Oct 20 '12 at 15:31
  • Oh, I've got it now... You were rigth, the problem was that my root element ParentID was equal to 0, and it should've been null – GaaRa Oct 20 '12 at 16:49
  • see this question: http://stackoverflow.com/questions/11565423/most-efficient-method-of-self-referencing-tree-using-entity-framework – Fredrik Jan 09 '17 at 14:47
  • I'm not completely sure, since I mostly work with fluent api by now, but it might be worth adding a `[InverseProperty("UObjects")]` to the `UParent` property when using data annotations. – grek40 Jan 10 '17 at 11:58

3 Answers3

7

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 
    }
mojorisinify
  • 377
  • 5
  • 22
Mohtisham Zubair
  • 723
  • 5
  • 15
  • I am achieving same behavior for categories & subcategories and many places where self referencing is required. Please feel free if still not getting desired results – Mohtisham Zubair Jan 10 '17 at 13:00
  • Initializing the collection in the constructor isn't strictly necessary. EF will create it when it's null. – Gert Arnold Jan 15 '17 at 17:20
2

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): Eager loading

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

Gebb
  • 6,371
  • 3
  • 44
  • 56
-3
  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.
Saboor Awan
  • 1,567
  • 4
  • 24
  • 37