-
Notifications
You must be signed in to change notification settings - Fork 3.3k
Description
Description
Assume we have 3 concrete and 1 abstract entity:
MainEntity
(concrete)EntityWithInheritance
(abstract)EntityWithInheritanceOne
(concrete, derives fromEntityWithInheritance
)EntityWithInheritanceTwo
(concrete, derives fromEntityWithInheritance
)
The hierarchy between them is: MainEntity
-> EntityWithInheritance
configured as 1:1
Assume we have an instance as follows: MainEntity
-> EntityWithInheritanceOne
saved in the database.
The discriminator column correctly denotes EntityWithInheritanceOne
.
We now want to update MainEntity
by replacing the existing property with one of type EntityWithInheritanceTwo
.
After calling SaveChanges
the discriminator column still incorrectly has the OLD value of EntityWithInheritanceOne
instead of the correct, new one.
All other (potential) properties seem to have updated correctly.
This did NOT happen in any EfCore version prior to version 7.
Repro
The following code in an excerpt from the linked repo
You will need to replace the connection string in MyContext
class to point to a valid SqlServer instance.
Database and config:
internal class MyContext : DbContext
{
public DbSet<MainEntity> MainEntities => Set<MainEntity>();
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(
@"Server=localhost,5433;Initial Catalog=EFCoreDatabaseTest2;User Id=sa;Password=Pass@word;TrustServerCertificate=true");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<EntityWithInheritance>()
.HasDiscriminator<string>(EntityWithInheritanceConfig.DiscriminatorColumnName)
.HasValue<EntityWithInheritanceOne>(EntityWithInheritanceConfig.EntityWithInheritanceOne)
.HasValue<EntityWithInheritanceTwo>(EntityWithInheritanceConfig.EntityWithInheritanceTwo);
modelBuilder.Entity<EntityWithInheritance>().HasOne<MainEntity>()
.WithOne(x => x.EntityWithInheritance)
.HasForeignKey<EntityWithInheritance>();
}
}
public class MainEntity
{
public Guid Id { get; set; }
public virtual EntityWithInheritance? EntityWithInheritance { get; set; }
}
public static class EntityWithInheritanceConfig
{
public const string EntityWithInheritanceOne = "EntityWithInheritanceOne";
public const string EntityWithInheritanceTwo = "EntityWithInheritanceTwo";
public const string DiscriminatorColumnName = "Discriminator";
}
public abstract class EntityWithInheritance
{
public Guid Id { get; set; }
public int MyNumber { get; set; }
}
public class EntityWithInheritanceOne : EntityWithInheritance
{
}
public class EntityWithInheritanceTwo : EntityWithInheritance
{
public int MyOtherNumber { get; set; }
}
And a program to test the problem:
using EFCoreDatabase.Contexts;
using Microsoft.EntityFrameworkCore;
await using (var ctx = new MyContext())
{
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
}
await using (var ctx2 = new MyContext())
{
var mainEntity = new MainEntity
{
EntityWithInheritance = new EntityWithInheritanceOne
{
MyNumber = 1
}
};
await ctx2.MainEntities.AddAsync(mainEntity);
await ctx2.SaveChangesAsync();
}
await using (var ctx3 = new MyContext())
{
var mainEntity = await ctx3
.MainEntities
.Include(x => x.EntityWithInheritance)
.FirstAsync();
mainEntity.EntityWithInheritance = new EntityWithInheritanceTwo
{
MyNumber = 11,
MyOtherNumber = 5
};
await ctx3.SaveChangesAsync();
}
await using (var ctx4 = new MyContext())
{
var mainEntity = await ctx4
.MainEntities
.Include(x => x!.EntityWithInheritance)
.FirstAsync();
if (mainEntity.EntityWithInheritance.GetType() != typeof(EntityWithInheritanceTwo))
throw new Exception("Invalid behavior");
}
Notice the exception being thrown when the incorrect type is returned.
Switch <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.0" />
to <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.10" />
or any other version prior to 7 and the code above works,
NOTE: The configuration above is the most default setup we could think of. Adding a MainEntityId
property to EntityWithInheritance
and then configuring that property as the foreign key to the MainEntity
table the code above also works. Probably because the FK and PK are now different. This is obviously undesirable, however, as it requires additional configuration and the PK is essentially useless.
Include provider and version information
EF Core version:
Database provider: Microsoft.EntityFrameworkCore.SqlServer 7.0.0
Target framework: (e.g. .NET 7.0)
Operating system: Windows
IDE: (e.g. Visual Studio 2022 17.4)