Entity Framework 6 trying to drop non existent Index when renaming - c#

As a newcomer to EF migrations, I was surprised by the following behaviour, and wondered if it's intentional (i.e. there's a switch to make it go away).
When I rename a column, I have the following relevant lines inside an EntityTypeConfiguration class:
Property(x => x.MyColumn).HasColumnName(#"MyColumn").HasColumnType("nvarchar").IsOptional();
And, crucially:
HasOptional(a => a.RelatedTable).WithMany(b => b.ThisTable).HasForeignKey(c => c.MyColumn).WillCascadeOnDelete(false);
Which is, as I understand it, establishing a foreign key relationship. When I rename MyColumn to MyColumn2, the migration that is created looks like this:
public override void Up()
{
RenameColumn(table: "dbo.ThisTable", name: "MyColumn", newName: "MyColumn2");
RenameIndex(table: "dbo.ThisTable", name: "IX_MyColumn", newName: "IX_MyColumn2");
}
However, MyColumn is not indexed on ThisTable. I realise that creating indexes for a foreign key relationship is advisable; is this why EF assumes there is one?
Note that the EF model was generated from the DB initially using the EF Reverse POCO Generator.

It's intentional. Code First migrations are based purely on model (data annotations, fluent configuration) and assume the previous database state is created using migration as well. Since EF default convention is to create index for FK columns, the migration assumes that the index exists and tries to rename it.
You can solve it in two ways. Either edit the generated migration and remove the RenameIndex (and other index related commands), or turn off (remove) the default FK index convention:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<ForeignKeyIndexConvention>();
// ...
}
Please note that the later will affect your future model modifications and you have to explicitly opt for index on FK columns (which cannot be done if the entity does not have explicit FK property). Also if you rename some of the exiting FK columns which do have an index, you'll have to add RenameIndex (or DropIndex/CreateIndex`) commands manually.

Related

Seed script in Entity Framework Core is breaking my migration

I have a very simple scenario where I have number of related entities and a couple of them are expected to be prefilled (through seed script). Then, I run my application and it adds a new related data (related to the tables populated by seed script).
And eventually, when I want to add a new migration, for example make a field nullable (of an entity which was not seeded), I try the update-database command and everything breaks up because the migration is trying to recreate my seeded tables. It is impossible because they keep a foreign key.
I will be more specific:
Car (Id, Brand, Color, Year)
many-to-one
CarType (Id, Value)
So, in my DbContext I have the following:
modelBuilder.Entity<CarType>()
.HasData(new CarType('4125ad9e-68fe-4d25-9d73-7e8acc097d6f', 'Coupe'))
And then, I run my application and I start inserting new Cars with the respective types. So, now I have:
Car(1, 'BMW', 'Black', 2011) FK-> CarType('4125ad9e-68fe-4d25-9d73-7e8acc097d6f', 'Coupe')
Car(2, 'Audi', 'Green', 2008) FK-> CarType('cc097d6f9e-68fe-4d25-9d73-4125ad7e8a', 'Estate')
But, one day I decide to make the Year field nullable
public int? Year { get; set; }
I ran add-migration and everything looks fine except for that in the Up statement:
migrationBuilder.DeleteData(
table: "CarType",
keyColumn: "Id",
keyValue: new Guid("4125ad9e-68fe-4d25-9d73-7e8acc097d6f"));
Once I execute update-database I get an error that the operation can not be executed because there's a foreign key related to the entity I am trying to delete:
The DELETE statement conflicted with the REFERENCE constraint
How am I supposed to handle that? My seed scrips are kind of blocking me right now. I want to be able to make changes to my other tables, even on the ones that are seed scripted without the need to recreate stuff.
I think you've records in DB depends on Card Type Id with the same Id you tried to delete
so you can manually delete all dependent entities from DB then apply update database and this not recommended
or
change all entities that have relation with CardType to be "Cascade" in onModelCreating function
example
entity.HasOne(d => d.CardTypes)
.WithMany(p => p.Cards)
.HasForeignKey(d => d.CardTypeId)
.OnDelete(DeleteBehavior.Cascade) /* Cascade */
.HasConstraintName("FK_Cards_CardTypes");
The solution for me was to remove the HasData related seeding from my DbContext because when a migration is added the changes to the data specified with HasData are transformed to calls to InsertData(), UpdateData(), and DeleteData().
So, as suggested HERE, I moved the initialization logic into my initial migration.
migrationBuilder.InsertData(
table: "CarType",
columns: new[] { "Id", "Value" },
values: new object[] { "4125ad9e-68fe-4d25-9d73-7e8acc097d6f", "Coupe" });

How to rename PrimaryKey and ForeignKey in EF Core 3.1?

I recently rename the column name on my DB. When create a new migration and what I see: EF Core immediately drop column and create a new one. It doesn't make sense to me. Is there any rename foreign key or primary key method?
Use "HasConstraintName" fluent API like this :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>()
.HasMany(c => c.Employees)
.WithOne(e => e.Company)
.HasConstraintName("NamFKConstraint");
}
MigrationsModelDiffer is the service class that detects model changes. It "works" by comparing two snapshots of your database model to guess what you changed. Column renames will probably only be detected if nothing else about the column was changed. If you make too many changes at once, then the missing columns will be dropped and new ones added.
If this isn't what you want, you'll need to modify the migration script to specify column renames & other type changes by hand.
migrationBuilder.RenameColumn(
name: "old_name",
table: "table_name",
newName: "new_name");

How to disable cascade delete for many-to-many relationships between some tables in Entity Framework 6?

My question is a bit similar to this, although I use EF6.
The problem is that I have two entities which are connected through a mapping table - and have a many-to-many relationship like this for example:
modelBuilder.Entity<Team>()
.HasMany(t => t.Members)
.WithMany()
.Map(c =>
{
c.ToTable("TeamMemberMapping");
});
So in this example one team can have multiple members and one guy can be a part of multiple teams.
The problem with this is when I delete a guy, all his team mappings will be deleted, because Entity Framework uses cascade delete by default as a delete action.
I'd like to turn this off - so the DB shouldn't allow to delete a guy, if he is a part of some team.
I know that I can remove the many-to-many cascade delete convention globally with this:
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
But this is too harsh for me. I would like to do this only for this table.
My other idea is (since I use code-first migrations) removing the convention just temporarily, so I can generate an update step which would drop all the foreign keys:
DropForeignKey("dbo.TeamMemberMapping", "Employee_Id", "dbo.Employee");
DropForeignKey("dbo.TeamMemberMapping", "Team_Id", "dbo.Team");
DropForeignKey("dbo.SomeOtherMapping", "Some_Id", "dbo.SomeTable");
DropForeignKey("dbo.SomeOtherMapping", "Other_Id", "dbo.OtherTable");
AddForeignKey("dbo.TeamMemberMapping", "Employee_Id", "dbo.Employee", "Id");
AddForeignKey("dbo.TeamMemberMapping", "Team_Id", "dbo.Team", "Id");
AddForeignKey("dbo.SomeOtherMapping", "Some_Id", "dbo.SomeTable", "Id");
AddForeignKey("dbo.SomeOtherMapping", "Other_Id", "dbo.OtherTable", "Id");
Now I can edit this migration to skip all the other mappings (like "SomeOtherMapping") and only deal with "TeamMemberMapping".
Then I would revert my temporary change (e.g. removing the convention)
Is there any drawbacks of this solution? Because it seems to be a workaround for me.
Is there any other solution?

Entity Framework change Id type

I've changed type of Id property from Int to Guid, no other entities reference that Id and I've manually deleted all records of that entity from database, my generated migration class looks like this:
public override void Up()
{
AlterColumn("dbo.RoutineExercises", "RoutineExerciseId", c => c.Guid(nullable: false));
}
public override void Down()
{
AlterColumn("dbo.RoutineExercises", "RoutineExerciseId", c => c.Int(nullable: false, identity: true));
}
And I'm getting this error when I run update-database command:
The object 'PK_dbo.RoutineExercises' is dependent on column 'RoutineExerciseId'. ALTER TABLE ALTER COLUMN RoutineExerciseId failed because one or more objects access this column.
And my FluentAPI configuration looks like this:
modelBuilder.Entity<RoutineExercise>().HasKey(r => r.RoutineExerciseId);
modelBuilder.Entity<RoutineExercise>()
.HasRequired(r => r.Exercise)
.WithMany()
.HasForeignKey(r => r.ExerciseId)
.WillCascadeOnDelete(true);
modelBuilder.Entity<RoutineExercise>()
.HasRequired(r => r.Routine)
.WithMany()
.HasForeignKey(r => r.RoutineId)
.WillCascadeOnDelete(true);
How can I fix this without dropping whole database ?
You might try doing the migration in stages - I don't know for sure but your fluent configuration is resulting in the creation of the primary key based on the field you want to change and the migration wizard can't factor in the need to drop the primary key convert the field to a guid and then rebuild a new primary key.
So my suggestion would be to do the migration in baby steps:
1. Remove the haskey from routineexerciseid and move it to another field or make a new composite key- ef needs a primary key
2. With the primary key moved you should be able to alter the column
3. Finally reinstate the primary key on that column.
Alternatively
Create a completely new column as a guid as the primary key, drop the old column and then if necessary rename the new column as required
I know this is an old question, but in case anyone else is looking for this answer, I just found it out myself. You need to drop the primary key before alters.
DropPrimaryKey("dbo.RoutineExercises",new[] { "RoutineExerciseId"});
It is easier to do a Migration Reset than changing a lot of things and possibly messing up your database. I do recommend making a back up of your data before proceeding with this though.
The process is outlined here:
https://weblog.west-wind.com/posts/2016/jan/13/resetting-entity-framework-migrations-to-a-clean-slate

Entity Framework issues - appends a "1" to my table name?

I have the following model-first (is that what it's called?) diagram that I have made. I use T4 to generate the classes.
Now, I have a problem that causes Entity Framework to somehow append a "1" to the table name of the DatabaseSupporter entity. The database has been generated from this very model, and nothing has been modified.
I am trying to execute the following line:
_entities.DatabaseSupporters.SingleOrDefault(s => s.Id == myId);
The error I receive when executing that line (along with its inner exception below) is:
An exception of type
'System.Data.Entity.Core.EntityCommandExecutionException' occurred in
mscorlib.dll but was not handled in user code.
Invalid object name 'dbo.DatabaseSupporter1'.
I tried fixing the problem with the following Fluent API code (notice the second line in the function that names the table explicitly to "DatabaseSupporter"), but with no luck.
protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
modelBuilder
.Entity<DatabaseSupporter>()
.HasOptional(f => f.DatabaseChatSession)
.WithOptionalPrincipal(s => s.DatabaseSupporter);
modelBuilder
.Entity<DatabaseSupporter>()
.Map(m =>
{
m.Property(s => s.Id)
.HasColumnName("Id");
m.ToTable("DatabaseSupporter");
});
modelBuilder
.Entity<DatabaseSupporter>()
.HasMany(s => s.DatabaseGroups)
.WithMany(g => g.DatabaseSupporters)
.Map(m =>
{
m.ToTable("DatabaseSupporterDatabaseGroup");
m.MapLeftKey("DatabaseGroups_Id");
m.MapRightKey("DatabaseSupporters_Id");
});
modelBuilder
.Entity<DatabaseGroup>()
.HasRequired(g => g.DatabaseChatProgram)
.WithMany(c => c.DatabaseGroups);
modelBuilder
.Entity<DatabaseGroup>()
.HasRequired(g => g.DatabaseOwner)
.WithMany(o => o.DatabaseGroups);
modelBuilder
.Entity<DatabaseOwner>()
.HasMany(o => o.DatabaseChatSessions)
.WithRequired(o => o.DatabaseOwner);
base.OnModelCreating(modelBuilder);
}
It should be mentioned that the Id property for every entity actually is a Guid.
I am using Entity Framework 6.0.2.
Any ideas?
Edit 1
Here's the generated DatabaseSupporter.cs file containing my DatabaseSupporter entity as requested in the comments.
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Coengage.Data.Entities
{
using System;
using System.Collections.Generic;
public partial class DatabaseSupporter
{
public DatabaseSupporter()
{
this.DatabaseGroups = new HashSet<DatabaseGroup>();
}
public bool IsActive { get; set; }
public string Username { get; set; }
public System.Guid Id { get; set; }
public virtual DatabaseChatSession DatabaseChatSession { get; set; }
public virtual ICollection<DatabaseGroup> DatabaseGroups { get; set; }
}
}
Edit 2
The errors started occuring after I added the many-to-many link between DatabaseSupporter and DatabaseGroup. Before that link, the Fluent code wasn't needed either.
This mapping is incorrect:
modelBuilder
.Entity<DatabaseSupporter>()
.Map(m =>
{
m.Property(s => s.Id)
.HasColumnName("Id");
m.ToTable("DatabaseSupporter");
});
It is kind of 50 percent of a mapping for Entity Splitting - a mapping that stores properties of a single entity in two (or even more) separate tables that are linked by one-to-one relationships in the database. Because the mapping is not complete you even don't get a correct mapping for Entity Splitting. Especially EF seems to assume that the second table that contains the other properties (that are not explicitly configured in the mapping fragment) should have the name DatabaseSupporter1. I could reproduce that with EF 6 (which by the way has added a Property method to configure single properties in a mapping fragment. In earlier versions that method didn't exist (only the Properties method).) Also the one-to-one constraints are not created correctly in the database. In my opinion EF should throw an exception about an incorrect mapping here rather than silently mapping the model to nonsense without exception.
Anyway, you probably don't want to split your entity properties over multiple tables but map it to a single table. You must then replace the code block above by:
modelBuilder.Entity<DatabaseSupporter>()
.Property(s => s.Id)
.HasColumnName("Id");
modelBuilder.Entity<DatabaseSupporter>()
.ToTable("DatabaseSupporter");
The first mapping seems redundant because the property Id will be mapped by default to a column with the same name. The second mapping is possibly also redundant (depending on if table name pluralization is turned on or not). You can try it without this mapping. In any case you shouldn't get an exception anymore that complains about a missing dbo.DatabaseSupporter1.
I have replicated your model exactly as you have listed it and I cannot currently reproduce your issue in the DDL that the EDMX surface emits when Generating Database from Model.
Could you please provide detailed information on exactly how you are going about adding your many-to-many relationship between DatabaseGroup and DatabaseSupporter? You say that you're trying to add the relationship on the edmx surface and NOT through code and it craps on your table name?
I added this thing Many-to-many from DatabaseGroup to DatabaseSupporter
I added this thing Many-to-many from DatabaseSupporter to DatabaseGroup
Can you please provide the following:
Rollback to your codebase prior to adding the many-to-many relationship. Ensure that your EF Fluent API code is not currently in your project.
Generate the DDL from this surface and confirm that it is not being
generated with the name DatabaseSupporters1 (Post the tablename that
it chooses at this stage. DatabaseSupporter or DatabaseSupporters)
Now, right click DatabaseGroup| Add New| Association
Choose DatabaseGroup for the left and DatabaseSupporter for the
right. Confirm that the name of the association that the designer
chooses is DatabaseGroupDatabaseSupporter [Do not create]
Choose DatabaseSupporter for the left and DatabaseGroup for the
right. Confirm that the name of the association that the designer
chooses is DatabaseSupporterDatabaseGroup [Create]
From the edmx surface, right click the many-to-many association just created and click "Show in Model Browser"
Edit your post to include the settings that display.
Also, right click the surface and click "Generate Database from Model."
Edit your post to include the DDL that gets generated. The table
should be named [DatabaseSupporters]
(My first inclination is that it's going to have something to do with your navigation properties, but not entirely sure. I actually had Entity Framework do the same thing to me in a toy project I was working on but I recall it being trivial to correct and I don't recall what the root cause was; I seem to recall it being something about the nav properties)
[Edit]
Wait.....
If I remove the many-to-many that doesn't fix my problem. However,
reverting to before I added the many-to-many fixes it. The exact code
that throws the exception is already shown. If I remove my fluent
mappings entirely, it's not the same exception being thrown (it throws
something about a group and a supporter, and a principal). I have not
tried recreating the model in an empty project - that takes a lot of
time. I already tried searching the EDMX in Notepad for references -
none were found.
(note my added emphasis)
So the DatabaseSupporter1 error showed up after you tried your fluent api patch? Get rid of the patch, add the many-to-many and give us the real error then.
...also, it took me 5 minutes to build this diagram. I wouldn't qualify that as "a lot of time."
I don't have my dev environment here in front of me, but my immediate thoughts are:
FIRST
Your fluent looks ok - but is the plural s in your ID column correct? And no plural (s) on the table names? This would be the opposite of convention.
SECOND
EF will automatically append a number to address a name collision. See similar question here: Why does EntityFramework append a 1 by default in edmx after the database entities?
Any chance you have something hanging around - a code file removed from your solution but still in your build path? Have you tried searching your source folder using windows explorer rather than the visual studio?
modelBuilder
.Entity<DatabaseSupporter>()
.HasMany(s => s.DatabaseGroups)
.WithMany(g => g.DatabaseSupporters)
.Map(m =>
{
m.ToTable("DatabaseSupporterDatabaseGroup");
m.MapLeftKey("DatabaseGroups_Id");
m.MapRightKey("DatabaseSupporters_Id");
});
Left and Right are inversed on Many to Many.
Try this :
modelBuilder
.Entity<DatabaseSupporter>()
.HasMany(s => s.DatabaseGroups)
.WithMany(g => g.DatabaseSupporters)
.Map(m =>
{
m.ToTable("DatabaseSupporterDatabaseGroup");
m.MapLeftKey("DatabaseSupporters_Id");
m.MapRightKey("DatabaseGroups_Id");
});
I think the DatabaseSupporter class created two time
one name is : DatabaseSupporter
another one is : DatabaseSupporter1
The modified changes are stored in DatabaseSupporter1 and mapping to here.
You need to copy the DatabaseSupporter1 class code and past the code to DatabaseSupporter class . then delete this DatabaseSupporter1 class.
I had this issue from renaming tables in the diagram, specifically changing just the capitalization.
If you rename a table by clicking on the header in the diagram, I think it checks the entity set name before trying to change it, sees it exists (even though it's the same entity set), and appends a 1.
However, if you right-click and open the Properties pane and first rename the Entity Set Name, then change the Name second, it won't add the number.
In my case i have two tables in the same database with the same name (2 different schemas(see image)

Categories

Resources