我的 ASP.NET Core Web API 应用程序使用 SQL Server,并且我在 Microsoft.DependencyInjection 中将以下服务注册为范围服务:
public class UpdateOperation<TEntity, TDto>(
IMapper mapper,
ApplicationDbContext dbContext,
ICurrentUtcTimeProvider currentUtcTimeProvider)
: IUpdateOperation<TEntity, TDto>
where TEntity : UserEntityBase
where TDto : DtoBase
{
private readonly IMapper _mapper = mapper;
private readonly ApplicationDbContext _dbContext = dbContext;
private readonly ICurrentUtcTimeProvider _currentUtcTimeProvider = currentUtcTimeProvider;
/// <inheritdoc />
public async Task UpdateAsync(TEntity? existingEntity, TDto newDto, string userId)
{
if (existingEntity is null)
return;
_mapper.Map(newDto, existingEntity);
if (existingEntity is IUpdateHistory entityUpdateHistory)
{
entityUpdateHistory.UpdatedAt = _currentUtcTimeProvider.GetCurrentUtcTime();
entityUpdateHistory.UpdatedBy = userId;
}
_dbContext.Update(existingEntity);
await _dbContext.SaveChangesAsync();
}
}
还有一个旧服务,它只是更改
IUpdateHistory
实体的一些属性:
public class UpdateInfoOperation(
ICurrentUtcTimeProvider currentUtcTimeProvider)
: IUpdateInfoOperation
{
private readonly ICurrentUtcTimeProvider _currentUtcTimeProvider = currentUtcTimeProvider;
/// <inheritdoc />
public void UpdateInfo(IUpdateHistory entity, string userId)
{
ArgumentNullException.ThrowIfNull(userId);
entity.UpdatedBy = userId;
entity.UpdatedAt = _currentUtcTimeProvider.GetCurrentUtcTime();
}
}
然后,我有使用其中一项服务的存储库服务。如果他们使用
UpdateInfoOperation
并自行调用 DbContext.Update() 方法,则一切正常:
/// <inheritdoc />
public async Task UpdateAsync(Guid id, RailLineDto newDto, string userId)
{
RailLine? existingEntity = await FindEntityByIdAsync(id, userId);
if (existingEntity == null)
return;
_mapper.Map(newDto, existingEntity);
_updateInfoOperation.UpdateInfo(existingEntity, userId);
_dbContext.Update(existingEntity);
await _dbContext.SaveChangesAsync();
}
/// <summary>
/// Asynchronously finds a RailLine entity by its identifier and user ID.
/// </summary>
/// <param name="id">The ID of the RailLine entity to find.</param>
/// <param name="userId">The ID of the user who owns the entity.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the RailLine entity if found; otherwise, null.</returns>
private async Task<RailLine?> FindEntityByIdAsync(Guid id, string userId)
{
return await
_dbContext.RailLines
.Include(l => l.Stations)
.Include(l => l.SpeedSections)
.Include(l => l.Gradients)
.Include(l => l.Curves)
.Include(l => l.Tunnels)
.Include(l => l.ElectrifiedSections)
.Include(l => l.Substations)
.FirstOrDefaultAsync(l => l.Id == id && l.UserId == userId && !l.IsDeleted);
}
但是当他们使用
UpdateOperation
并且上面的方法只是调用 UpdateOperation.UpdateAsync(...) 时,它调用 FindEntityByIdAsync(...) 作为其第一个参数,我最终会在数据库中得到新的和旧的子实体。例如,如果列表包含 2 个项目,并且更新后的列表也包含 2 个项目(无论是否已更改或未更改),则数据库中现在有 4 个项目,并且 GET 请求也返回 4 个项目。我应该改变什么才能使它与UpdateOperation
一起工作?
我尝试手动将EntityState切换为Modified,但没有成功。对我来说,代码看起来是一样的,我只是将它移到了其他地方,所以应该没有问题。还是我错过了什么?
当相关实体的 PK 被擦除或标记为
Added
而不是 Modified
时,就会发生重复行为。我怀疑此服务的 DbContext
实例正在解析为与存储库正在使用的实例不同的实例。 Update()
通常会自动开始跟踪处于 Modified
状态的相关实体,并且如果 DbContext
尚未跟踪该实体的实例,则它应该附加到单独的 DbContext
实例并在提供 PK 的情况下正常工作因为相关实体尚未被擦除。
一个可能的危险信号是 Mapper
.Map()
调用。您的映射器如何配置来处理子引用/集合?我会在 .Map(src, dest)
调用之后检查实体,特别是子集合,以查看它们的 PK 尚未被清除/默认,更重要的是,映射器 not 正在执行某些操作,将项目重新添加到集合中。例如,如果存储库和服务使用相同的 DbContext
实例,则 .Update()
完全没有必要,并且如果服务中的 Map 调用正在实例化子对象的新实例,即使设置了 PK,这也会通过 Added
更改跟踪标记为 DbContext
。 由于两种场景都使用了 Automapper,这是否使用完全相同的映射器配置?