You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Entity Framework Core 8.0.8中父类与[Owned]属性类的数据库更新问题

问题分析与解决方案

问题背景

使用EF Core的[Owned]特性将RefreshToken关联到User实体,保存更新时抛出DbUpdateConcurrencyException,提示"数据库操作预期影响1行,但实际影响0行"。

相关代码参考

实体定义

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public RefreshToken RefreshToken { get; set; }
}

[Owned]
public class RefreshToken
{
    public string Token { get; set; }
    public DateTime Expires { get; set; }
    public bool IsRevoked { get; set; }
}

业务与仓储代码

// 业务保存逻辑
public async Task SaveRefreshToken(User user, RefreshToken newToken)
{
    removeOldRefreshTokens(user);
    user.RefreshToken = newToken;
    await _userRepository.Update(user);
    await _unitOfWork.SaveChangesAsync();
}

// 移除旧令牌方法
private void removeOldRefreshTokens(User user)
{
    if (user.RefreshToken != null && user.RefreshToken.Expires < DateTime.UtcNow)
    {
        user.RefreshToken.IsRevoked = true;
    }
}

// 通用仓储Update方法
public async Task Update(T entity)
{
    _dbContext.Entry(entity).State = EntityState.Modified;
}

核心原因

  1. Owned实体追踪缺失:查询User时未加载RefreshToken,EF Core无法追踪Owned实体的变更,导致更新语句无有效字段。
  2. Update方法误用:直接设置EntityState.Modified会强制更新主实体所有字段,但Owned实体的变更可能未被正确识别。
  3. 并发令牌不匹配:若User配置了并发令牌(如[Timestamp]),实体内存值与数据库值不一致会触发并发异常。

解决方案

1. 确保查询时加载Owned实体

获取User必须包含RefreshToken,让EF Core能追踪其变更:

var user = await _userRepository.GetByIdAsync(userId, include: u => u.Include(u => u.RefreshToken));

2. 修正通用仓储Update逻辑

避免直接设置EntityState.Modified,改用值映射确保Owned实体变更被识别:

public async Task Update(T entity)
{
    var existingEntity = await _dbContext.Set<T>().FindAsync(GetEntityKey(entity));
    if (existingEntity != null)
    {
        _dbContext.Entry(existingEntity).CurrentValues.SetValues(entity);
        // 手动同步Owned实体属性
        if (entity is User user)
        {
            _dbContext.Entry(existingEntity.RefreshToken).CurrentValues.SetValues(user.RefreshToken);
        }
    }
}

// 辅助方法:获取实体主键值
private object GetEntityKey(T entity)
{
    return _dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties
        .Select(p => p.PropertyInfo.GetValue(entity)).FirstOrDefault();
}

3. 利用EF Core自动追踪简化代码

若User是上下文追踪的实体,无需手动调用Update,直接修改后保存即可:

public async Task SaveRefreshToken(int userId, RefreshToken newToken)
{
    var trackedUser = await _userRepository.GetByIdAsync(userId, include: u => u.Include(u => u.RefreshToken));
    removeOldRefreshTokens(trackedUser);
    trackedUser.RefreshToken = newToken;
    await _unitOfWork.SaveChangesAsync();
}

4. 并发冲突处理(可选)

若确为并发问题,可捕获异常后重试:

public async Task SaveRefreshToken(int userId, RefreshToken newToken)
{
    int retryCount = 3;
    while (retryCount > 0)
    {
        try
        {
            var trackedUser = await _userRepository.GetByIdAsync(userId, include: u => u.Include(u => u.RefreshToken));
            removeOldRefreshTokens(trackedUser);
            trackedUser.RefreshToken = newToken;
            await _unitOfWork.SaveChangesAsync();
            return;
        }
        catch (DbUpdateConcurrencyException)
        {
            retryCount--;
            if (retryCount == 0) throw;
        }
    }
}

内容的提问来源于stack exchange,提问作者Ashish

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 05:21:09