简体   繁体   中英

Code first Entity Framework (EF6) Adding Many-to-Many relationship after initial create

I am new to MVC, the Entity Framework, and Databases in general, but I have not been able to figure out what is going on here. I am trying to create a many-to-many relationship via code from an already created db. Thanks in advance for any pointers you can give.

I have a code-first database defined by 2 models (the real one is much more complex, but for brevity this also shows my question/problem):

public class Beer
{
    public int BeerID { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Company> Companies { get; set; }  // one beer could have many companies

}

public class Company
{
    public int CompanyID { get; set; }
    public string Name { get; set; }

    // Navigation Property

}

public class ManyToManyDB : DbContext
{
    public DbSet<Beer> Beers { get; set; }
    public DbSet<Company> Companies { get; set; }
}

When I run the Enable-Migrations -ContextTypeName ManyToManyTest.Models.ManyToManyDB, everything is fine. I get the Configuration.cs file in my project. I do some editing there:

    public Configuration()
    {
        AutomaticMigrationsEnabled = true;
        AutomaticMigrationDataLossAllowed = true;
    }

Add some data to the Seed method:

        context.Companies.AddOrUpdate(c => c.Name,
            new Company { Name = "Company1" },
            new Company { Name = "Company2" },
            new Company { Name = "Company3" }
            );

        context.Beers.AddOrUpdate(b => b.Name,
            new Beer { Name = "Beer1" },
            new Beer { Name = "Beer2" },
            new Beer { Name = "Beer3" }
            );

Now I run "Update-Database -verbose" and the DB is created. So far so good.

Now lets say I forgot to create my Many to Many relationship in the Company class, so I update it:

public class Company
{
    public int CompanyID { get; set; }
    public string Name { get; set; }

    // Navigation Property
    public virtual ICollection<Beer> Beers { get; set; }  // this is what I forgot, a company can have many beers
}

Ok, so, as this is early on in development, I should be able to just run "update-database" again and it will add the necessary stuff (which in this case will include a new junction table for the many-to-many relationship). Unfortunately, when I do, this happens:

EXECUTE sp_rename @objname = N'dbo.Companies', @newname = N'CompanyBeers', @objtype = N'OBJECT'

Caution: Changing any part of an object name could break scripts and stored procedures.

IF object_id(N'[dbo].[FK_dbo.Companies_dbo.Beers_Beer_BeerID]', N'F') IS NOT NULL
    ALTER TABLE [dbo].[Companies] DROP CONSTRAINT [FK_dbo.Companies_dbo.Beers_Beer_BeerID]

It throws this exception

System.Data.SqlClient.SqlException (0x80131904): Cannot find the object "dbo.Companies" because it does not exist or you do not have permissions.

The error being: Cannot find the object "dbo.Companies" because it does not exist or you do not have permissions.

Any idea why I cannot create a many-to-many relationship after creating the initial db schema?

I may just try again from the start, but wanted to know if there is a solution to this in case I happen upon it in the future.

Using VS2013 and EF6.02 if that matters. The project is from the normal MVC template.

Sorry to say but it seems like a bug in EF. EF generates automatic update that is equal to the one that would be produced by running Add-Migration.

    public override void Up()
    {
        RenameTable(name: "dbo.Companies", newName: "CompanyBeers");
        DropForeignKey("dbo.Companies", "Beer_BeerID", "dbo.Beers");
        DropIndex("dbo.Companies", new[] { "Beer_BeerID" });
        CreateIndex("dbo.CompanyBeers", "Company_CompanyID");
        CreateIndex("dbo.CompanyBeers", "Beer_BeerID");
        AddForeignKey("dbo.CompanyBeers", "Company_CompanyID", "dbo.Companies", "CompanyID", cascadeDelete: true);
        AddForeignKey("dbo.CompanyBeers", "Beer_BeerID", "dbo.Beers", "BeerID", cascadeDelete: true);
        DropColumn("dbo.Companies", "Beer_BeerID");
    }

Now in the second line of migration method it calls DropForeignKey that produces ALTER TABLE with DROP that ends with an exception because table was renamed in the first place.

The workaround is to run Add-Migration, drop code and write it by hand. I ended up with code like this:

    public override void Up()
    {
        DropIndex("dbo.Companies", new[] { "Beer_BeerID" });
        DropForeignKey("dbo.Companies", "Beer_BeerID", "dbo.Beers");
        DropColumn("dbo.Companies", "Beer_BeerID");

        CreateTable(
            "dbo.CompanyBeers",
            c => new
            {
                Company_CompanyID = c.Int(nullable: false),
                Beer_BeerID = c.Int(nullable: false),
            })
            .PrimaryKey(t => new { t.Company_CompanyID, t.Beer_BeerID })
            .ForeignKey("dbo.Companies", t => t.Company_CompanyID, cascadeDelete: true)
            .ForeignKey("dbo.Beers", t => t.Beer_BeerID, cascadeDelete: true)
            .Index(t => t.Company_CompanyID)
            .Index(t => t.Beer_BeerID);
    }

Answering my own question. After a few hours of thinking about it, like pg0xC states it is probably a bug in the Entity Framework. Here is an easy workaround in case anyone else hits this problem.

Sticking with my example above, remove both of the ICollections from the Beer and Company classes:

public class Beer
{
    public int BeerID { get; set; }
    public string Name { get; set; }
    //public virtual ICollection<Company> Companies { get; set; }  // one beer could have many companies

}

public class Company
{
    public int CompanyID { get; set; }
    public string Name { get; set; }

    // Navigation Property
    // public virtual ICollection<Beer> Beers { get; set; }  // this is what I forgot, a co
}

Now run update-database again... it worked for me on both the sample as well as my actual code. Of course you will lose any Companies that were linked to the beer class. If this is unacceptable, you will have to do something similar to what the other answerer (pg0xC) stated.

Go back and uncomment the ICollections in both the Beer and Company classes, run update-database one last time and everything should work. After doing this, I had the CompanyBeers junction table in the localdb.

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