Upgrading From EF 5 to 6, New Migration Swaps Columns - entity-framework

When upgrading a code first project from Entity Framework 5 to 6.1.1, a Model with 2 foreign keys to the same table results in the Entity Framework detecting a schema change when no change should be happening.
Here's the Model in question.
public class UserActivity : Activity
{
public string UserMessage { get; set; }
public int? OriginatorId { get; set; }
public int UserId { get; set; }
public virtual User Originator { get; set; }
public virtual User User { get; set; }
public class Configuration : EntityTypeConfiguration<UserActivity>
{
public Configuration()
{
HasOptional(x => x.Originator).WithMany().HasForeignKey(x => x.OriginatorId).WillCascadeOnDelete(false);
HasRequired(x => x.User).WithMany().HasForeignKey(x => x.UserId).WillCascadeOnDelete(false);
}
}
}
Here's the Migration that's generated when running the command Add-Migration from the Package Manager Console.
public partial class EF6 : DbMigration
{
public override void Up()
{
DropForeignKey("dbo.UserActivities", "UserId", "dbo.Users");
RenameColumn(table: "dbo.UserActivities", name: "UserId", newName: "__mig_tmp__0");
RenameColumn(table: "dbo.UserActivities", name: "OriginatorId", newName: "UserId");
RenameColumn(table: "dbo.UserActivities", name: "__mig_tmp__0", newName: "OriginatorId");
AlterColumn("dbo.UserActivities", "OriginatorId", c => c.Int());
AlterColumn("dbo.UserActivities", "UserId", c => c.Int(nullable: false));
AddForeignKey("dbo.UserActivities", "OriginatorId", "dbo.Users", "ID");
AddForeignKey("dbo.UserActivities", "UserId", "dbo.Users", "ID");
}
public override void Down()
{
DropForeignKey("dbo.UserActivities", "UserId", "dbo.Users");
DropForeignKey("dbo.UserActivities", "OriginatorId", "dbo.Users");
AlterColumn("dbo.UserActivities", "UserId", c => c.Int());
AlterColumn("dbo.UserActivities", "OriginatorId", c => c.Int(nullable: false));
RenameColumn(table: "dbo.UserActivities", name: "OriginatorId", newName: "__mig_tmp__0");
RenameColumn(table: "dbo.UserActivities", name: "UserId", newName: "OriginatorId");
RenameColumn(table: "dbo.UserActivities", name: "__mig_tmp__0", newName: "UserId");
AddForeignKey("dbo.UserActivities", "UserId", "dbo.Users", "ID", cascadeDelete: true);
}
}
Is there a bug in EF 6.1.1 that would explain this behavior? It looks like the EF 6 doesn't detect the 2 foreign keys properly.
As a work around, doing 2 separate migrations, the first dropping the foreign keys and a second migration to add the foreign keys seems to resolve the issue.

Related

How to configure one-to-one relationship in EF Core with FK on both ends

I have following entities:
public class Subscription
{
public int Id { get; set; }
public int? BillingContractId { get; set; }
public BillingContract BillingContract { get; set; }
//other properties
}
public class BillingContract
{
public int Id { get; set; }
public int SubscriptionId { get; set; }
public Subscription Subscription { get; set; }
//other properties
}
So each subscription might have only one billing contract and each billing contract belongs to a single subscription.
I'm trying to configure this relationship in my dbcontext:
builder.Entity<Subscription>()
.HasOne(subscription => subscription.BillingContract)
.WithOne(billingContract => billingContract.Subscription)
.HasForeignKey<BillingContract>(billingContract => billingContract.SubscriptionId)
.IsRequired(true);
builder.Entity<BillingContract>()
.HasOne(billingContract => billingContract.Subscription)
.WithOne(subscription => subscription.BillingContract)
.HasForeignKey<Subscription>(subscription => subscription.BillingContractId)
.IsRequired(false);
But from the generated migration(or from the snapshot or from the actual DB schema) I can tell that only FK in Subscription table is created. I cannot make EF to create a FK(and index) in the BillingContract table. I also tried to use annotation attributes with the same result.
Did I miss something? Or it's a bug in EF?
I'm using EF Core 2.2
To eliminate a possibility of a corrupted db snapshot I created a brand new console project using EF Core 3.1. After adding initial migration I have the same result with missing FK:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BillingContracts",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
SubscriptionId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_BillingContracts", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Subscriptions",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
BillingContractId = table.Column<int>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Subscriptions", x => x.Id);
table.ForeignKey(
name: "FK_Subscriptions_BillingContracts_BillingContractId",
column: x => x.BillingContractId,
principalTable: "BillingContracts",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Subscriptions_BillingContractId",
table: "Subscriptions",
column: "BillingContractId",
unique: true,
filter: "[BillingContractId] IS NOT NULL");
}
This is not an EF bug. Usually, two tables have an association relationship, and you only need to create one foreign key in one of the tables. The two-way foreign key is for the entity and does not exist in the database design. This docuement has give the detail example.

EF Core many to zero relationship configuration

How can I prevent EF Core migrations from adding a shadow property to TimeZone:
public partial class InitialCreate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Core_TimeZone",
schema: "dbo",
columns: table => new
{
StateOrProvinceId = table.Column<int>(nullable: true),
...
},
constraints: table =>
{
table.PrimaryKey("PK_Core_TimeZone", x => x.Id);
table.ForeignKey(
name: "FK_Core_TimeZone_Core_StateOrProvince_StateOrProvinceId",
column: x => x.StateOrProvinceId,
principalSchema: "dbo",
principalTable: "Core_StateOrProvince",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
}
}
Here are the POCO classes:
public partial class TimeZone : BaseEntity
{
…
// no StateOrProvince related properties
}
public partial class StateOrProvince : BaseEntity
{
…
private ICollection<Dna.NetCore.Core.BLL.Entities.Common.TimeZone> _timeZones;
public virtual ICollection<Dna.NetCore.Core.BLL.Entities.Common.TimeZone> TimeZones
{
get { return _timeZones ?? (_timeZones = new List<Dna.NetCore.Core.BLL.Entities.Common.TimeZone>()); }
set { _timeZones = value; }
}
}
and the configuration class:
public class StateOrProvinceConfiguration : IEntityTypeConfiguration<StateOrProvince>
{
public void Map(EntityTypeBuilder<StateOrProvince> builder)
{
…
builder.HasMany(d => d.TimeZones)
.WithOne()
.OnDelete(DeleteBehavior.Restrict);
}
}
I'm getting the shadow property regardless of whether or not I include the HasMany().WithOne() configuration.
The full source code is located in this GitHub repository.

context.database.create() throws exception invalid column name "Branch_Id"

I have a migration that got added once and applied successfully:
public partial class AddedTablesForBranchData : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.SalesArea",
c => new
{
PostalCode = c.Int(nullable: false, identity: true),
Location = c.String(),
Branch_Id = c.String(maxLength: 128),
})
.PrimaryKey(t => t.PostalCode)
.ForeignKey("dbo.SalesBranch", t => t.Branch_Id)
.Index(t => t.Branch_Id);
CreateTable(
"dbo.SalesBranch",
c => new
{
Id = c.String(nullable: false, maxLength: 128),
Name = c.String(),
Contacts = c.String(),
})
.PrimaryKey(t => t.Id);
}
public override void Down()
{
DropForeignKey("dbo.SalesArea", "Branch_Id", "dbo.SalesBranch");
DropIndex("dbo.SalesArea", new[] { "Branch_Id" });
DropTable("dbo.SalesBranch");
DropTable("dbo.SalesArea");
}
}
Sadly the the PostalCode is an integer. I had to change it to a string due to localization...
Thus some migrations later I added a new migration:
public partial class ReCreateSalesTables : DbMigration
{
public override void Up()
{
DropForeignKey("SalesArea", "Branch_Id", "SalesBranch");
DropIndex("SalesArea", new[] { "Branch_Id" });
DropTable("dbo.SalesArea");
DropTable("dbo.SalesBranch");
CreateTable("SalesBranch",
c => new
{
Id = c.String(false, maxLength: 128),
Name = c.String(),
Contacts = c.String()
})
.PrimaryKey(t => t.Id);
CreateTable("SalesArea",
c => new
{
Id = c.Int(false, true),
PostalCode = c.String(maxLength: 32),
Location = c.String(),
BranchId = c.String(nullable: false, maxLength: 128)
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.SalesBranch", t => t.BranchId)
.Index(t => t.PostalCode, unique: true);
}
public override void Down()
{
throw new Exception("It has never been our intention to use a down migration, else data might be lost...");
}
}
Then I run into the "a table con not have multiple identity columns" problem due to PostalCode being a identity column and in the new migration I have Id being a column Identity
Thus I had to drop both tables in the new migration and re-create those tables with the new schema.
There seems to be no problem on my local machine/development environment But when I run the integration tests or before any test is run I do this:
[TestClass]
public sealed class InitializeDatabase
{
[AssemblyInitialize]
public static void AssemblyInit(TestContext x)
{
using (var context = new LeadContext())
{
// Create database outside of the test transactions else you get a nice exception...
context.Database.Delete();
context.Database.Create();
new Configuration().FillEnums(context);
}
}
}
It is deleting the old database and creating a new database using all migrations. This AssemblyInit method runs fine when I debug it, but after leaving the method some second later I can see this output in my integration test:
Result Message: Initialization method IntegrationTests.SalesDataTests.Init threw exception. System.Data.Entity.Core.EntityCommandExecutionException: System.Data.Entity.Core.EntityCommandExecutionException: An error occurred while executing the command definition. See the inner exception for details. ---> System.Data.SqlClient.SqlException: Invalid column name 'Branch_Id'..
I am not able to debug my integration test directly as I never get there, so the problem must be the context.database.create() method.
Why is EF complaining that the old/former foreign key column 'Branch_Id' is invalid?
I do not understand that scenario.
Can anybody help please :-)
UPDATE
Question:
What changed between AddedTablesForBranchData and migration ReCreateSalesTables ?
Answer:
I introduced a property Id (identity column/string) and changed the property PostalCode to integer/unique:true.
UPDATE 2
There exist no fluent configurations about any SalesX table.
Model
[Table("SalesBranch")]
public class SalesBranch
{
[Key]
public string Id { get; set; }
public string Name { get; set; }
public string Contacts { get; set; }
public virtual ICollection<SalesArea> SalesAreas { get; set; }
}
[Table("SalesArea")]
public class SalesArea
{
public int Id { get; set; }
public string PostalCode { get; set; }
public string Location { get; set; }
public virtual SalesBranch Branch { get; set; }
public int BranchId { get; set; }
}
By annotating the property:
public virtual SalesBranch Branch { get; set; } with [ForeignKey("BranchId")] it fixed the problem!
It seemed the model were out of sync with the migrations!

Entity Framework code first can't update database after setting foreign key as nullable

I am having trouble updating my database using EF code first after I set foreign keys (ProcessID and SubProcessID) of my model as nullable.
Here's the model:
public class MyModel
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long MyModelID { get; set; }
[Display(Name = "Process")]
public int? ProcessID { get; set; }
[Display(Name = "Sub Process")]
public int? SubProcessID { get; set; }
//... some more properties here
public virtual Process Process { get; set; }
public virtual SubProcess SubProcess { get; set; }
}
Here's the migration code after I add migration:
public override void Up()
{
DropForeignKey("dbo.MyTable", "ProcessID", "dbo.Process");
DropForeignKey("dbo.MyTable", "SubProcessID", "dbo.SubProcess");
DropIndex("dbo.MyTable", new[] { "ProcessID" });
DropIndex("dbo.MyTable", new[] { "SubProcessID" });
AlterColumn("dbo.MyTable", "ProcessID", c => c.Int());
AlterColumn("dbo.MyTable", "SubProcessID", c => c.Int());
CreateIndex("dbo.MyTable", "ProcessID");
CreateIndex("dbo.MyTable", "SubProcessID");
AddForeignKey("dbo.MyTable", "ProcessID", "dbo.Process", "ProcessID");
AddForeignKey("dbo.MyTable", "SubProcessID", "dbo.SubProcess", "SubProcessID");
}
public override void Down()
{
DropForeignKey("dbo.MyTable", "SubProcessID", "dbo.SubProcess");
DropForeignKey("dbo.MyTable", "ProcessID", "dbo.Process");
DropIndex("dbo.MyTable", new[] { "SubProcessID" });
DropIndex("dbo.MyTable", new[] { "ProcessID" });
AlterColumn("dbo.MyTable", "SubProcessID", c => c.Int(nullable: false));
AlterColumn("dbo.MyTable", "ProcessID", c => c.Int(nullable: false));
CreateIndex("dbo.MyTable", "SubProcessID");
CreateIndex("dbo.MyTable", "ProcessID");
AddForeignKey("dbo.MyTable", "SubProcessID", "dbo.SubProcess", "SubProcessID", cascadeDelete: true);
AddForeignKey("dbo.MyTable", "ProcessID", "dbo.Process", "ProcessID", cascadeDelete: true);
}
The error message that I am getting is this:
Table 'MyDatabase.dbo.MyTable' doesn't exist
Note: I only failed to update-database when I set these foreign keys as nullable int. But if I make it as int, I can update-database successfully.
Thanks for your help!
If anyone of you having the same issue as me, Here's my solution to my problem:
In Migrations folder > Configuration.cs:
I changed Configuration constructor to this:
public Configuration()
{
AutomaticMigrationsEnabled = false;
SetSqlGenerator("MySql.Data.MySqlClient", new MySql.Data.Entity.MySqlMigrationSqlGenerator());
CodeGenerator = new MySql.Data.Entity.MySqlMigrationCodeGenerator();
}
And update-database now works!!
If anyone of you having trouble using code-first approach using MySQL, I am willing to help! :)

code first migration add one to many relationship

I currently have a "server" entity, defined as such :
public class EntityServer
{
public int Id { get; set; }
public string Name { get; set; }
}
I wanted to add a new "Host" entity, defined as such :
public class EntityHost
{
public int Id { get; set; }
public string Name { get; set; }
public string PublicIP { get; set; }
private ICollection<EntityServer> _servers;
public virtual ICollection<EntityServer> Servers
{
get { return _servers ?? (_servers = new HashSet<EntityServer>()); }
set { _servers = value; }
}
}
So i added
public virtual EntityHost Host { get; set; }
to my server entity to link those entities with a one to many relationship
modelBuilder.Entity<EntityHost>()
.HasMany<EntityServer>(x => x.Servers)
.WithRequired(x => x.Host);
And generated a migration acordingly :
public partial class MultiHosts : DbMigration
{
public override void Up()
{
CreateTable(
"dbo.EntityHosts",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
PublicIP = c.String(),
})
.PrimaryKey(t => t.Id);
AddColumn("dbo.EntityServers", "Host_Id", c => c.Int(nullable: false));
CreateIndex("dbo.EntityServers", "Host_Id");
AddForeignKey("dbo.EntityServers", "Host_Id", "dbo.EntityHosts", "Id", cascadeDelete: true);
}
public override void Down()
{
DropForeignKey("dbo.EntityServers", "Host_Id", "dbo.EntityHosts");
DropIndex("dbo.EntityServers", new[] { "Host_Id" });
DropColumn("dbo.EntityServers", "Host_Id");
DropTable("dbo.EntityHosts");
}
}
I've got some troubble setting a code first migration to add all it together as it outpout me a foreign key violation error when i try to access the context (which i understand as the server entity isn't linked to a host, as required by the model, because the hosts table is empty and I can't access the hosts entities to add one because of the FK violation ....)
So, my question is : how should I insert a default host entites for the existings server ?
As a trick you could first set the Server as Optional
modelBuilder.Entity<EntityHost>()
.HasOptional(x=>x.Server)
.WitMany(x => x.Hosts);
Run
Add-Migrations set_server_optional
update-Database
Update your Database and then change the Server as Required
modelBuilder.Entity<EntityHost>()
.HasRequired(x=>x.Server)
.WithMany(x => x.Hosts);
And finally
Add-Migrations set_server_required
update-Database