添加多关系Owned Entity时EF Core执行UPDATE而非INSERT引发并发异常
EF Core添加Owned Entity时执行UPDATE而非INSERT引发并发异常
添加Owned Entity时,EF Core执行UPDATE语句而非INSERT,导致并发异常。变更追踪器显示已跟踪新增实体,但实际执行的是UPDATE操作,无对应数据时触发异常。
简化类定义
public abstract class PaymentDemand { public Guid Id { get; set; } public SettlementTransactions? SettlementTransactions { get; set; } // 更多属性 } public class SettlementTransactions { public List<SettlementAllowance> ConsumedAllowances { get; set; } = new List<SettlementAllowance>(); public List<SettlementCharge> GeneratedCharges { get; set; } = new List<SettlementCharge>(); } public class SettlementAllowance { public Guid PaymentDemandId { get; set; } public Guid AccountTransactionId { get; set; } // 主键字段 /// <summary>The type of the transaction.</summary> public string TransactionType { get; set; } // 更多属性 }
实体配置
public void Configure(EntityTypeBuilder<PaymentDemand> builder) { builder.HasKey(p => p.Id); builder.Property(p => p.Id).ValueGeneratedNever(); builder.OwnsOne(p => p.SettlementTransactions, transactionBuilder => { transactionBuilder.OwnsMany(a => a.ConsumedAllowances, allowanceBuilder => { allowanceBuilder.WithOwner().HasForeignKey(t => t.PaymentDemandId); allowanceBuilder.HasKey(p => p.AccountTransactionId); allowanceBuilder.Property(p => p.Amount).HasColumnType("Money"); allowanceBuilder.ToTable("SettlementAllowances"); }); transactionBuilder.OwnsMany(c => c.GeneratedCharges, chargesBuilder => { chargesBuilder.WithOwner().HasForeignKey(t => t.PaymentDemandId); chargesBuilder.HasKey(p => p.AccountTransactionId); chargesBuilder.Property(p => p.AccountTransactionId).ValueGeneratedNever(); chargesBuilder.Property(p => p.Amount).HasColumnType("Money"); chargesBuilder.ToTable("SettlementCharges"); }); }); builder.Navigation(t => t.SettlementTransactions).IsRequired(); }
保存逻辑代码
public async Task DoThings(DbContext context, Guid id) { var demand = await context.PaymentDemands .Include(st => st.SettlementTransactions) .FirstOrDefaultAsync(d => d.Id == id, default); if (demand == null) { throw new InvalidOperationException(); } demand.SettleDate = DateTime.UtcNow; demand.SettlementTransactions ??= new SettlementTransactions(); demand.SettlementTransactions.ConsumedAllowances.Add(new SettlementAllowance{ TransactionType = "Blah" }); await context.SaveChangesAsync(); // 抛出异常 }
日志信息
变更追踪日志
DetectChanges starting for 'SqlBillingContext'. 2 entities were added and 0 entities were removed from navigation 'SettlementTransactions.ConsumedAllowances' on entity with key '{PaymentDemandId: d00544fe-c6c5-4b1d-a438-81508332af2d}'. Context 'SqlBillingContext' started tracking 'SettlementAllowance' entity with key '{AccountTransactionId: 296596b3-d45d-4753-b2f5-0ba7566f6800}'. Context 'SqlBillingContext' started tracking 'SettlementAllowance' entity with key '{AccountTransactionId: 7395965f-7fda-46d9-9e3b-3b428d4a7dce}'.
执行SQL日志
Executing update commands individually as the number of batchable commands (2) is smaller than the minimum batch size (4). Executing DbCommand [Parameters=[@p11='296596b3-d45d-4753-b2f5-0ba7566f6800', @p0='2022-10-25T09:34:41.7143804+00:00', @p1='49.5' (Precision = 3) (Scale = 1), @p2=NULL (Size = 4000), @p3='2022-12-24T09:18:36.3176378+00:00' (Nullable = true), @p4='6ca8c244-4438-4476-b417-ed501c378c0e' (Nullable = true), @p5='12032' (Nullable = true), @p6='d00544fe-c6c5-4b1d-a438-81508332af2d', @p7='df99b62b-d1b6-4d58-8c69-4df7eef33b61' (Nullable = true), @p8='d00544fe-c6c5-4b1d-a438-81508332af2d' (Nullable = true), @p9='2022-11-24T09:18:37.3176378+00:00' (Nullable = true), @p10='1'], CommandType='Text', CommandTimeout='30'] SET NOCOUNT ON; UPDATE [SettlementAllowances] SET [AccountingTime] = @p0, [Amount] = @p1, [Description] = @p2, [EndTime] = @p3, [InvoiceId] = @p4, [InvoiceNumber] = @p5, [PaymentDemandId] = @p6, [PaymentId] = @p7, [SourcePaymentDemandId] = @p8, [StartTime] = @p9, [TransactionType] = @p10 WHERE [AccountTransactionId] = @p11; SELECT @@ROWCOUNT;
排查思路与解决方案
- 确认主键赋值与生成策略:SettlementAllowance的主键
AccountTransactionId配置为ValueGeneratedNever(),EF不会自动生成主键值。如果新增实例时未显式设置唯一Guid,EF可能使用默认Guid值(如全0),或该Guid已存在于数据库中,导致EF误判实体为已存在,触发UPDATE。- 解决:新增SettlementAllowance时显式生成唯一Guid:
或修改主键配置为自动生成(若数据库支持):demand.SettlementTransactions.ConsumedAllowances.Add(new SettlementAllowance { AccountTransactionId = Guid.NewGuid(), TransactionType = "Blah", PaymentDemandId = demand.Id });allowanceBuilder.Property(p => p.AccountTransactionId).ValueGeneratedOnAdd();
- 解决:新增SettlementAllowance时显式生成唯一Guid:
- 验证实体追踪状态:在
SaveChangesAsync()前,检查新增实体的状态是否为Added:
若状态为var newAllowance = new SettlementAllowance{ TransactionType = "Blah", AccountTransactionId = Guid.NewGuid() }; demand.SettlementTransactions.ConsumedAllowances.Add(newAllowance); Console.WriteLine(context.Entry(newAllowance).State); // 应输出AddedModified,手动设置为Added:context.Entry(newAllowance).State = EntityState.Added; - 检查Owned Entity关系配置:
OwnsMany搭配WithOwner()时,确保外键关联逻辑正确。当前配置中ConsumedAllowances的外键是PaymentDemandId,需确认新增实体时该值已正确关联到PaymentDemand的Id,避免EF无法识别归属关系而误判状态。 - 升级EF Core版本:部分旧版本EF Core在处理Owned Entity集合时存在Bug,升级到最新稳定版可修复已知问题。
- 避免导航属性延迟加载问题:确保查询PaymentDemand时已通过
Include(st => st.SettlementTransactions)加载导航属性,防止新增时导航属性未被正确追踪。
内容的提问来源于stack exchange,提问作者Esben Bach
相关产品推荐
相关产品推荐

