EF Core:无法跟踪实体类型“xxxx”的实例...通过添加子项更新父记录时出错

问题描述 投票:0回答:1

我遇到了一个非常奇怪的错误,非常感谢您的帮助。我目前正在使用.net7 开发一个新的winforms 应用程序。我们正在使用 EF core 与 MS Acces DB 文件集成(别无选择)。我构建了一个查询现有 BusinessDate 实体对象的服务,然后向其中添加子 OtcVariationMarginRecords。我一直在单元和集成测试(XUnit)中专门致力于此。在集成测试中,我遇到了一个问题:直到几天前,该服务都完美运行,将所有记录按预期保存在数据库文件中。然后两天前,我开始收到这个错误:

System.InvalidOperationException:无法跟踪实体类型“OtcVariationMarginRecord”的实例,因为已跟踪具有相同键值 {'Id'} 的另一个实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。考虑使用“DbContextOptionsBuilder.EnableSensitiveDataLogging”来查看冲突的键值。

我尝试过的:

  • 我们最近将 Microsoft.EntityFrameworkCore.Tools 包从 7.0.9 更新到 7.0.10。我尝试将其回滚,但问题仍然存在。最近没有什么改变。
  • 我创建了具有最少属性的简单父/子模型对象,运行迁移,并在尝试通过添加子项更新现有父项时收到相同的错误。因此,问题不在于我的保证金模型。
  • 我们注入一个复杂的 _dbContextService 包装器/接口(不是我创建的)来处理各种审计跟踪更新。我已完全删除它并引用具体的 dbContext。没修好。
  • 我对 EF core 比较陌生,所以我阅读了这里的所有内容。我尝试缩短 dbContext 的寿命(参见下面的代码),但返回了相同的错误。
  • 我还尝试了这个这个这个。使用 AsNoTracking() 只会导致没有 OtcVariationMarginRecord 记录保存到数据库中。与 _context.ChangeTracker.Clear() 相同。

我感到完全被难住了,希望这里有人能指出我的代码中的明显缺陷。非常感谢。

实体模型:

[Index(nameof(Date), IsUnique = true)]
public class BusinessDate : BaseEntity
{
    public DateTime Date { get; set; }
    public List<OtcVariationMarginRecord> OtcVariationMarginRecords { get; set; } = new();
}

public class OtcVariationMarginRecord : BaseEntity
{   
    public decimal BaseCurrencyExposure { get; set; }
    public CurrencyCode BaseCurrency { get; set; }
    public decimal TransferAmount { get; set; }
    public MarginCallNature CallNature { get; set; }
    public MarginDirection Direction { get; set; }
    public decimal RoundedTransferAmount { get; set; }

    // i have removed a bunch of properties as part of my trouble shooting.
}
public abstract class BaseEntity : IBaseEntity
{
    [Key]
    public int Id { get; set; }
    public virtual RecordStatus Status { get; set; }
    [NotMapped]
    public DateTime? LastModifiedDateTimeUtc { get; set; }
    [NotMapped]
    public int? LastModifiedBy { get; set; }
    [NotMapped]
    public string? LastModifiedByName { get; set; }
}

简化使用:

private EfCoreTrackingTestContext _context;

public async Task GenerateMarginRecordsAsync(DateTime asOfDate)
{
    // i instantiate here instead of inject just for troubleshooting
    await using (_context = InstantiateContext())
    {
        var businessDate = await _context
            .BusinessDates
            //.AsNoTracking() // this resulted in nothing being saved
            .Include(bd => bd.OtcVariationMarginRecords)            
            .SingleOrDefaultAsync(bd => bd.Date == asOfDate)
            .ConfigureAwait(false);

        // nothing here touches the db nor entities that already exist
        var newMarginRecords = CalculationLogic();

        targetBusinessDate.OtcVariationMarginRecords.AddRange(newMarginRecords);
        
        // error thrown here
        await _context.SaveChangesAsync().ConfigureAwait(false);        
    }
}
private static EfCoreTrackingTestContext InstantiateContext()
{
    var optionsBuilder = new DbContextOptionsBuilder<EfCoreTrackingTestContext>();
    var options = optionsBuilder
        .UseJet(TestDbConfig.ConnectionString.IntegrationTestMarginDb)
        .Options;
    return new EfCoreTrackingTestContext(options);
}
c# entity-framework entity-framework-core .net-7.0 c#-11.0
1个回答
0
投票

在 OtcVariationMarginRecord 表中,EF 会自动添加与 FK Business Date(父表)相关的字段,因为它是子表。你可以自己验证一下(我试过)。最好修改一下类,添加FK,这样我们就可以在后面的代码中引用它:

public class OtcVariationMarginRecord : BaseEntity
{
    public decimal BaseCurrencyExposure { get; set; }
    public decimal BaseCurrency { get; set; }
    public decimal TransferAmount { get; set; }
    public string CallNature { get; set; }
    public bool Direction { get; set; }
    public decimal RoundedTransferAmount { get; set; }
    public int BusinessDateId { get; set; }
    public BusinessDate BusinessDate { get; set; }           
}

最后,你可以试试这个代码:

public async Task GenerateMarginRecordsAsync(DateTime asOfDate)
{                                                
    await using (_context = InstantiateContext())
    {
        var targetbusinessDate = await _context
            .BusinessDates                                            
            .SingleOrDefaultAsync(bd => bd.Date == asOfDate)
            .ConfigureAwait(false);

        int myId = targetbusinessDate.Id;
        _context.Entry(targetbusinessDate).State = EntityState.Unchanged;

        var newMarginRecords = CalculationLogic();
      
        foreach (OtcVariationMarginRecord mr in newMarginRecords)
        {
            mr.Id = 0;
            mr.BusinessDateId = myId;
            _context.OtcVariationMarginRecords.Add(mr);
        }
                        
        await _context.SaveChangesAsync().ConfigureAwait(false);
    }
}

我将新的 OtcVariationMarginRecords 直接添加到其表中,而不包括父表中的现有记录。 我希望这会对您有所帮助。

© www.soinside.com 2019 - 2024. All rights reserved.