Search code examples
c#asp.netentity-frameworkentity-framework-6eager-loading

Entity framework many-to-many relation table created "backwards"


I'm having an issue with EF6 and many-to-many relations. I have a following setup:

public class Foo
{
    public int Id { get; set; }
    public virtual ICollection<Bar> Bars { get; set; }
    public virtual ICollection<SubBar> SubBars { get; set; }

    public Foo()
    {
        Bars = new HashSet<Bar>();
        SubBars = new HashSet<SubBar>();
    }
}

public class Bar
{
    public int Id { get; set; }
    public virtual ICollection<Foo> Foos { get; set; }

    public Bar()
    {
        Foos = new HashSet<Foo>();
    }
}

public class SubBar
{
    public int Id { get; set; }
    public virtual ICollection<Foo> Foos { get; set; }

    public SubBar()
    {
        Foos = new HashSet<Foo>();
    }
}

The relation between Foo and Bar works properly, and there is a table called BarFoos in the DB. However the relation between Foo and SubBar is reversed. There is a table in the DB called FooSubBars which basically holds the correct relations, but eager loading, like SubBar.AsQueryable().Include(sb => sb.Foos) returns an EntityCommandExecutionException with a message Invalid object name dbo.SubBarFoos.

Question is: How do I reverse the relation table name to allow eager loading?
--Edit--
DB creation in the migration is as follows:

CreateTable(
            "dbo.BarFoos",
            c => new
                {
                    Bar_Id = c.Int(nullable: false),
                    Foo_Id = c.Int(nullable: false),
                })
            .PrimaryKey(t => new { t.Bar_Id, t.Foo_Id })
            .ForeignKey("dbo.Bars", t => t.Bar_Id, cascadeDelete: true)
            .ForeignKey("dbo.Foos", t => t.Foo_Id, cascadeDelete: true)
            .Index(t => t.Bar_Id)
            .Index(t => t.Foo_Id);

CreateTable(
            "dbo.FooSubBars",
            c => new
                {
                    Foo_Id = c.Int(nullable: false),
                    SubBar_Id = c.Int(nullable: false),
                })
            .PrimaryKey(t => new { t.Foo_Id, t.SubBar_Id })
            .ForeignKey("dbo.Foos", t => t.Foo_Id, cascadeDelete: true)
            .ForeignKey("dbo.SubBars", t => t.SubBar_Id, cascadeDelete: true)
            .Index(t => t.Foo_Id)
            .Index(t => t.SubBar_Id);

Would replacing the table name in the migration be sufficient?


Solution

  • You can ensure the table name is generated as "FooBars" if you override the OnModelCreating method on your DbContext as follows:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    
        modelBuilder.Entity<Foo>().HasMany(f => f.Bars).WithMany(b => b.Foos)
                .Map(m => 
                    m.ToTable("FooBars")
                    // Optionally specify the key column names...
                    .MapLeftKey("FooId") 
                    .MapRightKey("BarId")
                );
    
        modelBuilder.Entity<Foo>().HasMany(f => f.SubBars).WithMany(sb => sb.Foos).Map(m => m.ToTable("FooSubBars"));
    }
    

    Which will produce this in the migration:

            CreateTable(
                "dbo.FooBar",
                c => new
                    {
                        FooId = c.Int(nullable: false),
                        BarId = c.Int(nullable: false),
                    })
                .PrimaryKey(t => new { t.FooId, t.BarId })
                .ForeignKey("dbo.Foos", t => t.FooId, cascadeDelete: true)
                .ForeignKey("dbo.Bars", t => t.BarId, cascadeDelete: true)
                .Index(t => t.FooId)
                .Index(t => t.BarId);
    
            CreateTable(
                "dbo.FooSubBar",
                c => new
                    {
                        Foo_Id = c.Int(nullable: false),
                        SubBar_Id = c.Int(nullable: false),
                    })
                .PrimaryKey(t => new { t.Foo_Id, t.SubBar_Id })
                .ForeignKey("dbo.Foos", t => t.Foo_Id, cascadeDelete: true)
                .ForeignKey("dbo.SubBars", t => t.SubBar_Id, cascadeDelete: true)
                .Index(t => t.Foo_Id)
                .Index(t => t.SubBar_Id);
    

    I also tried eager loading the Foos when querying SubBars on the DbContext with don't get any errors:

    var context = new FooBarContext();
    
    var subBars = from sb in context.SubBars.Include(i => i.Foos)
                  select sb;