在调用 SaveChangesAsync() 时在一对多关系中添加子项会导致异常
Adding child within One-to-Many relationship results in exception when SaveChangesAsync() is called
首先,我尝试在 SO 和其他地方找到我的问题的解决方案,但其中 none 可以解决我的问题。我从生产代码中制作了我的问题的简化版本,以使其更易于理解
我有一个名为 WorkEntity
的父实体。它与名为 QuotationEntity
的实体具有一对一的关系。此 QuotationEntity
与 QuotationLine.
具有一对多关系 到目前为止一切顺利。让我们看看 classes 长什么样。首先是 WorkEntity
:
public class WorkEntity : Entity, IAggregateRoot
{
public string Name { get; set; }
public QuotationEntity QuotationEntity { get; set; }
public WorkEntity()
{
QuotationEntity = new QuotationEntity();
}
}
然后QuotationEntity
public class QuotationEntity : Entity
{
public string Name { get; set; }
public Guid WorkEntityId { get; set; }
public WorkEntity WorkEntity { get; set; }
public ICollection<QuotationLine> Lines { get; set; } = new List<QuotationLine>();
}
最后但并非最不重要的 QuotationLine
:
public class QuotationLine : Entity
{
public string Price { get; set; }
public Guid QuotationEntityId { get; set; }
public QuotationEntity QuotationEntity { get; set; }
}
让我们看看我的测试代码是什么样子的,以及我遇到的异常是什么:
[Fact]
public async Task TestAddingChildElement()
{
var repo = new WorkEntityRepository(DbContext);
var workEntity = new WorkEntity();
repo.Insert(workEntity);
await DbContext.SaveChangesAsync();
var workFromDb = await repo.GetAsync(workEntity.Id);
workFromDb.QuotationEntity.Lines.Add(new QuotationLine());
await DbContext.SaveChangesAsync();
}
我的测试相当简单。首先,我创建了 WorkEntity
及其对应的 QuotationEntity
而没有 QuotationLine
。持久化这个实体后,我用相应的存储库检索它,然后我尝试将 QuotationLine
添加到 QuotationEntity
。第二次调用 DbContext.SaveChangesAsync()
时,出现以下异常:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 数据库操作预期影响 1 行但实际上影响了 0 行。自加载实体以来,数据可能已被修改或删除。有关理解和处理乐观并发异常的信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=527962。
WorkRepository
class 看起来像这样:
public class WorkEntityRepository : IWorkEntityRepository
{
private readonly DbSet<WorkEntity> _entities;
private readonly WarehousingDbContext _dbContext;
public WorkEntityRepository(WarehousingDbContext dbContext)
{
if (dbContext == null)
{
throw new ArgumentNullException(nameof(dbContext));
}
_dbContext = dbContext;
_entities = dbContext.Set<WorkEntity>();
}
public Task<WorkEntity> GetAsync(Guid id)
{
return GetWorkEntityIncludingChilds().SingleAsync(p => p.Id == id);
}
public WorkEntity Insert(WorkEntity workEntity)
{
var entity = _entities.Add(workEntity);
return entity.Entity;
}
private IQueryable<WorkEntity> GetWorkEntityIncludingChilds()
{
return _entities
.Include(c => c.QuotationEntity)
.ThenInclude(w => w.Lines);
}
}
为了配置 WorkEntity
和 QuotationEntity
之间的一对一关系,我使用以下代码片段:
builder.HasOne(a => a.QuotationEntity)
.WithOne(b => b.WorkEntity)
.HasForeignKey<QuotationEntity>(b => b.WorkEntityId);
最后但同样重要的是,我在内存中使用 SQLite 数据库和 EF Core 3.1 来重现此问题:
private void SetUpInMemorySqlLiteDb(ServiceCollection services)
{
services.AddDbContext<WarehousingDbContext>(o => o
.UseSqlite("Data Source=:memory:")
.EnableSensitiveDataLogging());
}
由于我花了很多时间试图解决这个问题,所以我有一些发现:
此异常的一个可能原因可能是从存储库检索 WorkEntity
时未包含子实体。如果我不包括它们,那么实体不被 EF ChangeTracker 跟踪是合乎逻辑的。但如您所见,我包括了所有子实体。
我在调试过程中发现 QuotationLine
没有得到正确的状态。如果我将其状态明确设置为:
var quotationLine = new QuotationLine();
DbContext.Entry(quotationLine).State = EntityState.Added;
workFromDb.QuotationEntity.Lines.Add(quotationLine);
然后一切都按预期工作。但我认为这不是解决方案,它只是一个 hacky 解决方法。据我所知,EFCore 应该处理元素的 ChangeTracking。
让我知道,如果需要,我可以为我的问题提供更多代码。在此先感谢您的帮助!
But I think it is not the solution, it is just a hacky workaround. The EFCore should handle the ChangeTracking of the element as far as I know.
我觉得你应该改变主意了!
我对这个问题做了一些研究。因此,我能说的是,它可能是 EF Core 本身或其 sqlite 提供程序中的一些已知错误。查看这些票以了解更多信息:
https://github.com/dotnet/efcore/issues/9166
https://github.com/dotnet/efcore/issues/9803#issuecomment-391315406
首先,我尝试在 SO 和其他地方找到我的问题的解决方案,但其中 none 可以解决我的问题。我从生产代码中制作了我的问题的简化版本,以使其更易于理解
我有一个名为 WorkEntity
的父实体。它与名为 QuotationEntity
的实体具有一对一的关系。此 QuotationEntity
与 QuotationLine.
具有一对多关系 到目前为止一切顺利。让我们看看 classes 长什么样。首先是 WorkEntity
:
public class WorkEntity : Entity, IAggregateRoot
{
public string Name { get; set; }
public QuotationEntity QuotationEntity { get; set; }
public WorkEntity()
{
QuotationEntity = new QuotationEntity();
}
}
然后QuotationEntity
public class QuotationEntity : Entity
{
public string Name { get; set; }
public Guid WorkEntityId { get; set; }
public WorkEntity WorkEntity { get; set; }
public ICollection<QuotationLine> Lines { get; set; } = new List<QuotationLine>();
}
最后但并非最不重要的 QuotationLine
:
public class QuotationLine : Entity
{
public string Price { get; set; }
public Guid QuotationEntityId { get; set; }
public QuotationEntity QuotationEntity { get; set; }
}
让我们看看我的测试代码是什么样子的,以及我遇到的异常是什么:
[Fact]
public async Task TestAddingChildElement()
{
var repo = new WorkEntityRepository(DbContext);
var workEntity = new WorkEntity();
repo.Insert(workEntity);
await DbContext.SaveChangesAsync();
var workFromDb = await repo.GetAsync(workEntity.Id);
workFromDb.QuotationEntity.Lines.Add(new QuotationLine());
await DbContext.SaveChangesAsync();
}
我的测试相当简单。首先,我创建了 WorkEntity
及其对应的 QuotationEntity
而没有 QuotationLine
。持久化这个实体后,我用相应的存储库检索它,然后我尝试将 QuotationLine
添加到 QuotationEntity
。第二次调用 DbContext.SaveChangesAsync()
时,出现以下异常:
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 数据库操作预期影响 1 行但实际上影响了 0 行。自加载实体以来,数据可能已被修改或删除。有关理解和处理乐观并发异常的信息,请参阅 http://go.microsoft.com/fwlink/?LinkId=527962。
WorkRepository
class 看起来像这样:
public class WorkEntityRepository : IWorkEntityRepository
{
private readonly DbSet<WorkEntity> _entities;
private readonly WarehousingDbContext _dbContext;
public WorkEntityRepository(WarehousingDbContext dbContext)
{
if (dbContext == null)
{
throw new ArgumentNullException(nameof(dbContext));
}
_dbContext = dbContext;
_entities = dbContext.Set<WorkEntity>();
}
public Task<WorkEntity> GetAsync(Guid id)
{
return GetWorkEntityIncludingChilds().SingleAsync(p => p.Id == id);
}
public WorkEntity Insert(WorkEntity workEntity)
{
var entity = _entities.Add(workEntity);
return entity.Entity;
}
private IQueryable<WorkEntity> GetWorkEntityIncludingChilds()
{
return _entities
.Include(c => c.QuotationEntity)
.ThenInclude(w => w.Lines);
}
}
为了配置 WorkEntity
和 QuotationEntity
之间的一对一关系,我使用以下代码片段:
builder.HasOne(a => a.QuotationEntity)
.WithOne(b => b.WorkEntity)
.HasForeignKey<QuotationEntity>(b => b.WorkEntityId);
最后但同样重要的是,我在内存中使用 SQLite 数据库和 EF Core 3.1 来重现此问题:
private void SetUpInMemorySqlLiteDb(ServiceCollection services)
{
services.AddDbContext<WarehousingDbContext>(o => o
.UseSqlite("Data Source=:memory:")
.EnableSensitiveDataLogging());
}
由于我花了很多时间试图解决这个问题,所以我有一些发现:
此异常的一个可能原因可能是从存储库检索
WorkEntity
时未包含子实体。如果我不包括它们,那么实体不被 EF ChangeTracker 跟踪是合乎逻辑的。但如您所见,我包括了所有子实体。我在调试过程中发现
QuotationLine
没有得到正确的状态。如果我将其状态明确设置为:var quotationLine = new QuotationLine(); DbContext.Entry(quotationLine).State = EntityState.Added; workFromDb.QuotationEntity.Lines.Add(quotationLine);
然后一切都按预期工作。但我认为这不是解决方案,它只是一个 hacky 解决方法。据我所知,EFCore 应该处理元素的 ChangeTracking。
让我知道,如果需要,我可以为我的问题提供更多代码。在此先感谢您的帮助!
But I think it is not the solution, it is just a hacky workaround. The EFCore should handle the ChangeTracking of the element as far as I know.
我觉得你应该改变主意了!
我对这个问题做了一些研究。因此,我能说的是,它可能是 EF Core 本身或其 sqlite 提供程序中的一些已知错误。查看这些票以了解更多信息:
https://github.com/dotnet/efcore/issues/9166
https://github.com/dotnet/efcore/issues/9803#issuecomment-391315406