如何禁用EF中枚举对应实体UnitOfMeasure的跟踪功能?
我开发的新系统包含若干枚举类型,为保证引用完整性,没有仅以int类型存储枚举值,而是创建了对应的查找表(与枚举值一一对应,记录固定不变),使其能自动插入数据库。但在单元测试中批量插入对象时,出现如下错误:
System.InvalidOperationException: The instance of entity type 'UnitOfMeasure' cannot be tracked because another instance with the key value '{Id: Box}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
相关代码
public class Line : EntityBase { public int BillId { get; set; } public int Quantity { get; set; } public UnitOfMeasureEnum UnitOfMeasureId { get; set; } public virtual UnitOfMeasure UnitOfMeasure { get; set; } = null!; public IList<LineItem> LineItems { get; set; } = new List<LineItem>(); } public class LineConfiguration : IEntityTypeConfiguration<Line> { public void Configure(EntityTypeBuilder<Line> builder) { // Primary Key builder.HasKey(x => x.Id); builder.Property(x => x.Id) .HasColumnOrder(0); // ... snip ... builder.Property(x => x.UnitOfMeasureId) .HasColumnOrder(8); builder.HasOne(x => x.UnitOfMeasure) .WithMany() .HasForeignKey(x => x.UnitOfMeasureId) .OnDelete(DeleteBehavior.Restrict); builder.HasMany(x => x.LineItems); } } public void Configure(EntityTypeBuilder<UnitOfMeasure> builder) { builder.ToTable( TableName, x => x.HasComment("Table representation of UnitOfMeasureEnum for referential integrity. Do not edit without matching change to Enum in code.")); builder.Property(x => x.Id) .ValueGeneratedNever(); builder.HasData( new List<UnitOfMeasure> { new() { Id = UnitOfMeasureEnum.Bin, Name = "Bin" }, new() { Id = UnitOfMeasureEnum.Box, Name = "Box" }, new() { Id = UnitOfMeasureEnum.Case, Name = "Case" }, new() { Id = UnitOfMeasureEnum.Carton, Name = "Carton" }, new() { Id = UnitOfMeasureEnum.Drum, Name = "Drum" }, new() { Id = UnitOfMeasureEnum.Each, Name = "Each" }, new() { Id = UnitOfMeasureEnum.Lot, Name = "Lot" }, new() { Id = UnitOfMeasureEnum.Pail, Name = "Pail" }, new() { Id = UnitOfMeasureEnum.Piece, Name = "Piece" }, new() { Id = UnitOfMeasureEnum.Pallet, Name = "Pallet" }, new() { Id = UnitOfMeasureEnum.Roll, Name = "Roll" }, new() { Id = UnitOfMeasureEnum.Skid, Name = "Skid" }, }); } public class UnitOfMeasure { public UnitOfMeasureEnum Id { get; set; } public string Name { get; set; } }
针对这种固定不变的枚举查找表,有几种实用的解决办法:
禁用该实体的跟踪
在UnitOfMeasure的配置类中添加无跟踪设置,让EF始终不跟踪该实体的实例:public void Configure(EntityTypeBuilder<UnitOfMeasure> builder) { // 原有配置代码... builder.HasNoTracking(); // 添加此配置 }这是最彻底的解决方式,符合查找表固定不变的特性。
批量插入时仅设置外键值
因为UnitOfMeasure的记录是预插入且固定的,批量创建Line对象时,只需要给UnitOfMeasureId赋值枚举值即可,无需实例化UnitOfMeasure对象并赋值给导航属性:// 正确的批量创建示例 var lines = new List<Line> { new Line { BillId = 1, Quantity = 5, UnitOfMeasureId = UnitOfMeasureEnum.Box }, new Line { BillId = 2, Quantity = 3, UnitOfMeasureId = UnitOfMeasureEnum.Each } // 不需要设置UnitOfMeasure属性 }; dbContext.Lines.AddRange(lines); dbContext.SaveChanges();这种方式避免了EF尝试跟踪查找表实体,从根源上解决冲突。
手动附加实体并设置状态(适合必须跟踪的场景)
如果业务上需要保留跟踪,批量插入前可以先将需要用到的查找表实体附加到上下文,并设置为Unchanged状态,确保上下文只跟踪一个实例:// 预先附加需要用到的枚举实体 var boxUom = new UnitOfMeasure { Id = UnitOfMeasureEnum.Box }; dbContext.Attach(boxUom).State = EntityState.Unchanged; var eachUom = new UnitOfMeasure { Id = UnitOfMeasureEnum.Each }; dbContext.Attach(eachUom).State = EntityState.Unchanged; // 再进行批量插入操作这种方法相对繁琐,仅推荐在必须跟踪查找表实体的场景下使用。
内容的提问来源于stack exchange,提问作者Killnine

