EF Core migration creates incorrect enums - c#

What can be the reason that EF Core can incorrectly read db context and set wrong enum types for database?
I had DB-First and scaffolded context and entities in my project.
After it was found out there are unmapped enums in my db and my context, I created enums in Pascal case and mapped them to tables like this:
modelBuilder
.HasPostgresEnum<AutoStatus>()
.HasPostgresEnum<AnotherEnum>()
.HasPostgresEnum<AndAnotherEnum>();
modelBuilder.Entity<Auto>(entity =>
entity.Property(e => e.Status).HasColumnName("status")
.HasConversion<string>()
.HasDefaultValueSql(AutoStatus.InAssembly.ToString())
.HasColumnType(nameof(AutoStatus));
);
Next was created an "Initial" migration and I tried to update (create) database by this migration. But EF spat out error "42704: type "autostatus" does not exist".
So, migration, migration designer and model snapshot had another enums for some reason:
Migration:
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:Enum:auto_status", "in_assembly,ready_to_test,in_test,ready_to_sale,sold")
Migration Designer:
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "auto_status", new[] { "in_assembly", "ready_to_test", "in_test", "ready_to_sale", "sold" });
Context Snapshot
NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "auto_status", new[] { "in_assembly", "ready_to_test", "in_test", "ready_to_sale", "sold" });
Why still there are old enums, if nowhere in project aren't uses of old (snakecase) enums?

Related

How can EF Core prevent seeding from being invoked multiple times in OnModelCreating()?

According to documentation,
Typically OnModelCreating() is called only once when the first instance of a derived context is created. The model for that context is then cached and is for all further instances of the context in the app domain.
Now consider my code:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.ApplyConfiguration(new StudentConfiguration());
}
internal class StudentConfiguration : IEntityTypeConfiguration<Student>
{
public void Configure(EntityTypeBuilder<Student> builder)
{
Console.WriteLine($">>>>>>>>>>>>>>>>>>>>>> {nameof(StudentConfiguration)}");
builder.HasData(
new Student
{
Name = "Albert Einstein",
Age = 100
},
new Student
{
Name = "Isaac Newton",
Age = 400
}
);
}
}
When invoking dotnet ef database update, the seeding is invoked. So far it is understandable.
Now if I start the application, OnModelCreating() should be invoked to create models that will be cached for all further instances of the database context in the application domain.
I see a single
>>>>>>>>>>>>>>>>>>>>>> StudentConfiguration
for the first database access in the whole life of the application.
However, I don't see a duplicate seeding in the database.
Restarting the application multiple times also does not cause multiple seeding. It is actually good but how can seeding happens just once recall that Configure() is invoked multiple times, each once per application domain?
HasData fluent API is part of the so called EF Core Model Data Seed. Per documentation
Unlike in EF6, in EF Core, seeding data can be associated with an entity type as part of the model configuration. Then EF Core migrations can automatically compute what insert, update or delete operations need to be applied when upgrading the database to a new version of the model.
Note
Migrations only considers model changes when determining what operation should be performed to get the seed data into the desired state. Thus any changes to the data performed outside of migrations might be lost or cause an error.
All that means that HasData calls inside OnModelCreating are used only for generating migrations (and are applied to the database only when these migrations are executed). They have no effect on runtime behavior of the db context/model/sets/CRUD operations.
If you enabled your ef migrations it will create __EFMigrationsHistory table and insert your migrations to table in your database. And if your seed migration invoked it will insert to table so there can't be any rerun or duplicate your migrations.
If you wanna try to rerun the migration, delete your migration row from __EFMigrationsHistory table and update database or run your app again. But be careful if your migration change your table design it will throw error.
Table Name:
Migrations should look like this:

Avoid using discriminator in EF Core when dealing with inheritance

Problems started after switching from EF to EF Core (3.1).
I have a base abstract class and a derived class which is created dynamicaly in runtime (using reflection).
entity configuration of my base type was (EF):
ToTable("TableName", "dbo");
HasKey(t => t.Id);
HasRequired(t => t.prop1).WithMany().HasForeignKey(t => t.prop1);
Property(t => t.prop2).IsRequired();
Property(t => t.prop3).IsRequired();
I built base class with this configuration and dynamic class with modelBuilder.Entity(type).
And everything worked fine. I could get instances of my base class using context.Objects and instances of the dynamic class using Activator.CreateInstance(type).
Now I have same configuration but for EF Core:
builder.ToTable("TableName", "dbo");
builder.HasKey(t => t.Id);
builder.HasOne(t => t.prop1).WithMany().HasForeignKey(t => t.prop1);
builder.Property(t => t.prop2).IsRequired();
builder.Property(t => t.prop3).IsRequired();
But in EF Core getting objects from context gives an error "Invalid column name 'Discriminator'". Yes, I don't have discriminator column in my table (apparently it's required when TPH pattern is used) but it worked perfectly without it in EF. How did EF dealt with inheritance in that case? Moreover, creating such column and populating it with the same data (derived class name) seems to be useless. It feels like there should be something I'm missing.
So, my question is:
Is there any way to fix the problem without creating a discriminator column?
A default EF TPH will generally go across to EF Core without too many issues, however the customisation options are different between the two, for instance in core we can now easily manipulate the discriminator via fluent notation: https://www.learnentityframeworkcore.com/configuration/fluent-api/hasdiscriminator-method
Check that your base class is NOT abstract: https://stackoverflow.com/a/34646164/1690217
If your base class IS abstract then you will have to manually configure the Discriminator column: https://www.learnentityframeworkcore.com/inheritance/table-per-hierarchy#configuration
Also check that your database schema from the previous EF migrations actually has the Discriminator column and that it is a string type, the actual values should be the name of the types, however it is possible that you have configured or applied conventions elsewhere that override the default behaviour (in either the EF or the EF Core implementations)
If you include the actual schema in the database or the migration entries that build the tables you might get a more definitive answer.

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 map an entity for a table that already has a mapped entity and avoid EF conflicts?

I want to map a simplified read-only entity (e.g. for UI dropdowns, that need only id and name) to a table that already has full-feature entity mapped.
I have fairly typical mapping configurations using IEntityTypeConfiguration classes that map entities through EntityTypeBuilder<MyFullClass> and EntityTypeBuilder<MySimpleClass>.
I have no control over database, it's a legacy project and I cannot add new SQL views just to solve this code issue.
public class MyFullClassConfiguration : IEntityTypeConfiguration<MyFullClass>
{
public void Configure(EntityTypeBuilder<MyFullClass> builder)
{
builder.ToTable("MyTable");
... all properties mapped
public class MySimpleClassConfiguration : IEntityTypeConfiguration<MySimpleClass>
{
public void Configure(EntityTypeBuilder<MySimpleClass> builder)
{
builder.ToTable("MyTable");
... minimum of required properties mapped
When I run the project, I get an error:
Cannot use table 'MyTable' for entity type 'MySimpleClass' since it is being used for entity type 'MyFullClass' and there is no relationship between their primary keys.
I tried to artificially link both entities, adding one-to-one relation:
b.HasOne<MyFullClass>().WithOne().HasForeignKey<MySimpleClass>(e => e.Id);
This time the project was started normally, I could read and update entities, but when saving a new MyFullClass, EF threw:
The entity of type 'MyFullClass' is sharing the table 'MyTable' with entities of type 'MySimpleClass', but there is no entity of this type with the same key value that has been marked as 'Added'.
This seems so common scenario - to return simplified versions of complex entities for performance and bandwidth reasons, so I was surprised to discover that it's not supported in EF and that they will implement it only in v3, if I'm not mistaken: https://github.com/aspnet/EntityFrameworkCore/issues/15310
How do I solve this in .NET Core 2.2?

Entity Framework 6 trying to drop non existent Index when renaming

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.

Categories

Resources