EF Fluent API建模多外键关联:调整新增数据SQL执行顺序
问题:EF Core Fluent API 多对多关联插入顺序不符合预期
问题背景
现有三张表结构:
Exam表
| Id | Data |
|---|---|
| 1 | row data |
ExamTrackedItem表
| Id | TrackedValue | Sha1Hash |
|---|---|---|
| 4 | abc | 0x23123 |
ExamExamTrackedItem表(中间关联表)
| Id | ExamId | TrackedFieldId |
|---|---|---|
| 1 | 1 | 4 |
对应的实体类定义:
Exam类
public class Exam : Entity, IAggregateRoot { public int Id { get; private set; } private readonly List<ExamExamTrackedItem> _examExamTrackedItems; }
ExamTrackedItem类
public class ExamTrackedItem { public int Id { get; set; } public string Value { get; set; } public byte[] Sha1Hash { get; set; } }
ExamExamTrackedItem类
public class ExamExamTrackedItem { public int Id { get; set; } public int ExamId { get; set; } public int ExamTrackedItemId { get; set; } public ExamTrackedItem TrackedItem { get; set; } }
期望实现Exam.AddTrackedItem(ExamTrackedItem item)方法,完成新增ExamTrackedItem记录并建立中间表关联,但当前Fluent API配置生成的SQL插入顺序错误:
错误SQL:
SET NOCOUNT ON; INSERT INTO [medData].[ExamExamTrackedItem] ([ExamId], [ExamTrackedItemId], [TrackedItemType]) OUTPUT INSERTED.[Id] VALUES (@p0, @p1, @p2); INSERT INTO [medData].[ExamTrackedItem] ([Id], [Sha1Hash], [Value]) VALUES (@p3, @p4, @p5);
期望SQL顺序:先插入ExamTrackedItem获取ID,再插入中间表:
INSERT INTO [medData].[ExamTrackedItem] ([Id], [Sha1Hash], [Value]) VALUES (@p3, @p4, @p5); DECLARE @v_newId int = SCOPE_IDENTITY() INSERT INTO [medData].[ExamExamTrackedItem] ([ExamId], [ExamTrackedItemId], [TrackedItemType]) VALUES (@p0, @v_newId, @p2)
解决方案
1. 修正实体关系映射逻辑
当前使用OwnsOne将ExamTrackedItem标记为ExamExamTrackedItem的从属实体,这不符合实际的独立实体关系。ExamTrackedItem是独立表,应通过外键与中间表ExamExamTrackedItem关联,而非从属关系。
2. 修改Fluent API配置
移除OwnsOne配置,改为配置ExamExamTrackedItem与ExamTrackedItem的外键关系:
internal sealed class ExamEntityTypeConfiguration : IEntityTypeConfiguration<Exam> { public void Configure(EntityTypeBuilder<Exam> builder) { builder.ToTable("ExamRoot", "medData"); builder.HasKey(b => b.Id); // 保留原有ExamMedService配置 builder.OwnsMany<ExamMedService>("_examMedServices", x => { x.WithOwner().HasForeignKey("ExamId"); x.ToTable("ExamMedService", "medData"); x.Property<decimal?>("Price").HasColumnName("Price"); x.HasKey("Id"); }); // 修正ExamExamTrackedItems关联配置 builder.OwnsMany<ExamExamTrackedItem>("_examExamTrackedItems", x => { x.WithOwner().HasForeignKey("ExamId"); x.ToTable("ExamExamTrackedItem", "medData"); x.Property<ExamTrackedItemType>("TrackedItemType").HasColumnName("TrackedItemType"); x.Property<int>("ExamTrackedItemId").HasColumnName("ExamTrackedItemId"); x.HasKey("Id"); // 配置与ExamTrackedItem的外键关联 x.HasOne<ExamTrackedItem>() .WithMany() .HasForeignKey(e => e.ExamTrackedItemId) .OnDelete(DeleteBehavior.Cascade); // 根据业务需求调整删除行为 }); } }
同时添加ExamTrackedItem的独立配置:
internal sealed class ExamTrackedItemEntityTypeConfiguration : IEntityTypeConfiguration<ExamTrackedItem> { public void Configure(EntityTypeBuilder<ExamTrackedItem> builder) { builder.ToTable("ExamTrackedItem", "medData"); builder.HasKey(e => e.Id); builder.Property(e => e.Value).HasColumnName("Value"); builder.Property(e => e.Sha1Hash).HasColumnName("Sha1Hash"); } }
3. 实现AddTrackedItem方法
确保Exam类中的集合已初始化,并创建关联实体:
public class Exam : Entity, IAggregateRoot { public int Id { get; private set; } private readonly List<ExamExamTrackedItem> _examExamTrackedItems = new(); public void AddTrackedItem(ExamTrackedItem item) { var association = new ExamExamTrackedItem { TrackedItem = item // 按需设置TrackedItemType字段 }; _examExamTrackedItems.Add(association); } }
4. 验证插入顺序
修改后,EF Core会自动识别实体依赖关系:ExamExamTrackedItem依赖ExamTrackedItem的ID,因此会先插入ExamTrackedItem获取自增ID,再插入中间表,生成的SQL与预期一致。
内容的提问来源于stack exchange,提问作者lasjan
相关产品推荐
相关产品推荐

