-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
EFCore 7.0.0 - Graph of entity insertion throws a FK exception #29741
Comments
/cc @roji Still fails on latest daily. |
This seems to be a TPC-related metadata bug... We have both ModuleVariantA and ModuleVariantB (TPC hierarchy) referencing ModuleHost (TPH hierarchy). However, looking at the ReferencingForeignKeyConstraints for ModuleHost's table, I can see one foreign key constraint from iq2 (ModuleVariantB), but the other one from iq4 (ModuleVariableA) is missing. This causes the topological sort to not add a foreign key edge, which causes both INSERTs to be executed in the same batch, causing the exception. @AndriySvyryd, I think you'll want to take it from here... Here's a simplified single-file repro, including the metadata lookup: var ctx = GetContext();
var relationalModel = ctx.Model.GetRelationalModel();
var table = relationalModel.FindTable("ModuleHost", "common");
foreach (var foreignKeyConstraint in table.ReferencingForeignKeyConstraints)
{
// Should show two constraints, shows one.
Console.WriteLine(foreignKeyConstraint.Table);
}
var address = new ModuleVariantA // <= The child
{
ModuleIdentifier = "ModuleIdentifier",
ModuleHost = new ModuleHostA // <= The Parent
{
Modules = new List<ModuleVariantA>() { },
}
};
ctx.Attach(address);
ctx.SaveChanges();
Console.ReadLine();
static MyContext GetContext()
{
var factory = new MyContextFactory();
var ctx = factory.CreateDbContext(Array.Empty<string>());
// ctx.Database.EnsureDeleted();
// ctx.Database.EnsureCreated();
return ctx;
}
public class MyContextFactory : IDesignTimeDbContextFactory<MyContext>
{
public MyContext CreateDbContext(string[] args)
{
// var connectionString = "User Id=XX;Password=XX;TrustServerCertificate=True;data source=localhost;initial catalog=EFCore.Test;MultipleActiveResultSets=True;App=EntityFramework";
var connectionString = "Server=localhost;Database=test;User=SA;Password=Abcd5678;Connect Timeout=60;ConnectRetryCount=0;Encrypt=false";
var options = new DbContextOptionsBuilder<MyContext>()
.UseSqlServer(connectionString, x => x.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery))
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging()
.Options;
return new MyContext(options);
}
}
public class MyContext : DbContext
{
public DbSet<ModuleHost> ModuleHosts { get; set; } = default!;
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<TimeSpan>()
.HaveConversion<string>();
base.ConfigureConventions(configurationBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("common");
modelBuilder.Entity<ModuleHost>(x =>
{
x.Ignore(x => x.ModuleHostType);
x.UseTphMappingStrategy();
x.HasDiscriminator(x => x.ModuleHostTypeName)
.HasValue<ModuleHostC>(ModuleHostType.VariantB.ToString())
.HasValue<ModuleHostA>(ModuleHostType.VariantA.ToString())
.HasValue<ModuleHostB>(ModuleHostType.VariantC.ToString())
.HasValue<UnknownModuleHost>(ModuleHostType.None.ToString())
.IsComplete(false);
x.HasKey(x => x.Id);
x.Property(x => x.Id).UseIdentityColumn();
});
modelBuilder.Entity<ModuleBase>().UseTpcMappingStrategy();
modelBuilder.Entity<ModuleVariantBase>().UseTpcMappingStrategy();
modelBuilder.Entity<ModuleVariantA>()
.HasOne(x => x.ModuleHost as ModuleHostA)
.WithMany(x => x.Modules)
.IsRequired()
.HasForeignKey(x => x.ModuleHostId);
modelBuilder.Entity<ModuleVariantB>()
.HasOne(x => x.ModuleHost as ModuleHostC)
.WithMany(x => x.ModuleVariantBs)
.IsRequired()
.HasForeignKey(x => x.ModuleHostId);
base.OnModelCreating(modelBuilder);
}
}
public abstract record class ModuleBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; init; }
public ModuleHost ModuleHost { get; set; }
public int ModuleHostId { get; set; }
public string? ModuleIdentifier { get; init; }
}
[Table("ModuleHost", Schema = "common")]
public class ModuleHost
{
[Key]
public int Id { get; init; }
// public Request Requests { get; set; }
// public Guid ReadingRequestGuid { get; set; }
public virtual ModuleHostType ModuleHostType { get; set; }
public string ModuleHostTypeName
{
set => ModuleHostType = (ModuleHostType)Enum.Parse(typeof(ModuleHostType), value, true);
get => ModuleHostType.ToString();
}
}
public class ModuleHostA : ModuleHost
{
public override ModuleHostType ModuleHostType { get => ModuleHostType.VariantA; }
public virtual ICollection<ModuleVariantA> Modules { get; init; } = new List<ModuleVariantA>();
}
public class ModuleHostB : ModuleHostA
{
public override ModuleHostType ModuleHostType { get => ModuleHostType.VariantA; }
}
public class ModuleHostC : ModuleHost
{
public override ModuleHostType ModuleHostType { get => ModuleHostType.VariantC; }
public virtual ICollection<ModuleVariantB> ModuleVariantBs { get; init; } = new List<ModuleVariantB>();
}
public class UnknownModuleHost : ModuleHost
{
}
[Flags]
public enum ModuleHostType
{
None = 0,
VariantBB = 0b0000_0001 | VariantB,
VariantB = 0b0000_1000,
VariantA = 0b0001_0000,
VariantC = 0b0010_0000,
}
public abstract record class ModuleVariantBase : ModuleBase
{
public double? ValueField { get; init; }
}
[Table("ModuleVariant", Schema = "iq4")]
public record class ModuleVariantA : ModuleVariantBase
{
public double? ValueField { get; init; }
}
[Table("ModuleVariant", Schema = "iq2")]
public record class ModuleVariantB : ModuleVariantBase
{
public double? RemoteLan { get; init; }
public double? AlarmAdress { get; init; }
public double? TextOnOff { get; init; }
public string? SupplyFrequency { get; init; }
public string? ReferenceVolts { get; init; }
} |
Any temporary workaround idea? |
/cc @AndriySvyryd |
There isn't a reasonable workaround I can come up with |
@AndriySvyryd Should we patch then? |
When trying to insert an entity with it's parent , efcore tries to insert two entities in one go, the twos have a relationship with a foreign key references. Maybe the mix of TPC and TPH trigger this error?
Repo for reproduction : https://github.com/AlexandreRevel/EfCoreBug
Stack traces
Include provider and version information
EF Core version: 7.0.0
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: net7.0
Operating system: 10.0.19042
IDE: Visual studio 2022 17.4.1
The text was updated successfully, but these errors were encountered: