Search code examples
.netentity-framework-4.1mappingmany-to-manyentity-relationship

ADO EF Code First Bi-Directional Many-To-Many Self Reference Challenge


I've been kicking the tyres on ADO EF Code First and have come up with the following issue:

My model quite simply represents a family tree. It consists of a Person Class with a few scalar properties FirstName etc and a collection of Person representing Parents and another collection of Person representing Children.

public class Person
{
    public int id { get; set; }
    public string FirstName { get; set; }
    public string MiddleName { get; set; }
    public string LastName { get; set; }
    public DateTime? DateOfBirth { get; set; }

    List<Person> children = new List<Person>();
    public IList<Person> Children { get { return children; } set { children = (List<Person>)value; } }

    List<Person> parents = new List<Person>();
    public IList<Person> Parents { get { return parents; } set { parents = (List<Person>)value; } }
}

The context code is quite simple

public class DataContext : DbContext
{
    public DbSet<Person> People { get; set; }
}

The DataBase schema that gets created is as expected. A People Table with 2 one-to-many relationships to a table named PersonPersons. (By the way, is there anyway to change this to a table named FamilyTree?)

I then proceed to Seed the DataBase with some data, a family tree for the Popular Simpsons cartoon show. I create all the people I require, relate them and then save the person variable representing Homer Simpson.

class ProjectDBInitializer : DropCreateDatabaseIfModelChanges<DataContext>
{
    protected override void Seed(DataContext context)
    {
        Person ph = new Person() { FirstName = "Homer", LastName = "Simpson" };
        Person pm = new Person() { FirstName = "Marge", LastName = "Simpson" };

        Person pb = new Person() { FirstName = "Bart", LastName = "Simpson" };
        Person pl = new Person() { FirstName = "Lisa", LastName = "Simpson" };

        Person phf = new Person() { FirstName = "GranPa", LastName = "Simpson" };
        Person phm = new Person() { FirstName = "GranMa", LastName = "Simpson" };

        Person pmf = new Person() { FirstName = "Marge Father", LastName = "Maiden" };
        Person pmm = new Person() { FirstName = "Marge Mother", LastName = "Maiden" };

        phf.Children.Add(ph);
        phm.Children.Add(ph);

        ph.Children.Add(pb);
        ph.Children.Add(pl);

        pb.Parents.Add(ph);
        pb.Parents.Add(pm);

        pl.Parents.Add(ph);
        pl.Parents.Add(pm);

        pm.Parents.Add(pmf);
        pm.Parents.Add(pmm);

        context.People.Add(ph);
        context.SaveChanges();
    }
}

The problem comes into play when when I look at what's gone into the DataBase.

All of Marge's Family have been inserted but Homer's parents (GranPa and GranMa) have not. I assume this has to do with the fact that the ph varibale has no reference to them even though they hold a reference to it in thier Children collection.

What is the best (easiest) way of going about solving this problem?


Solution

  • To reneme the table you must add fluent mapping to your context:

    public class DataContext : DbContext
    {
        public DbSet<Person> People { get; set; }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
    
            modelBuilder.Entity<Person>()
                .HasMany(p => p.Children)
                .WithMany(c => c.Parents)
                .Map(m => m.ToTable("FamilyTree"));
        }
    }
    

    The problem with insert is exactly as you describe. If you want to insert Homer you must set phf and phm as his parents or you must add phm and phf to People set in the same way as you added Homer.

    Automatically generated POCO objects (T4 templates used to generate POCOs from EDMX) use more complex code for navigation properties and they internally handle relation fixup so if you add Homer to child collection in phm it will in turn automatically add phm to Homer's parents. If you want such functionality in your own POCOs you must implement it by yourselves. Fixup methods are not always win-win solution because they have some drawback and sometimes triggers lazy loading without real need to load data.