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
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.
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.
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!
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! :)
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