为什么 EF Core 的 DbContext.Update() 将原始子实体保留在数据库中,而其他时候则不然?

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

我的 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,但没有成功。对我来说,代码看起来是一样的,我只是将它移到了其他地方,所以应该没有问题。还是我错过了什么?

c# entity-framework-core asp.net-core-webapi
1个回答
0
投票

当相关实体的 PK 被擦除或标记为

Added
而不是
Modified
时,就会发生重复行为。我怀疑此服务的
DbContext
实例正在解析为与存储库正在使用的实例不同的实例。
Update()
通常会自动开始跟踪处于
Modified
状态的相关实体,并且如果
DbContext
尚未跟踪该实体的实例,则它应该附加到单独的
DbContext
实例并在提供 PK 的情况下正常工作因为相关实体尚未被擦除。

一个可能的危险信号是 Mapper

.Map()
调用。您的映射器如何配置来处理子引用/集合?我会在
.Map(src, dest)
调用之后检查实体,特别是子集合,以查看它们的 PK 尚未被清除/默认,更重要的是,映射器 not 正在执行某些操作,将项目重新添加到集合中。例如,如果存储库和服务使用相同的
DbContext
实例,则
.Update()
完全没有必要,并且如果服务中的 Map 调用正在实例化子对象的新实例,即使设置了 PK,这也会通过
Added
更改跟踪标记为
DbContext
。 由于两种场景都使用了 Automapper,这是否使用完全相同的映射器配置?

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