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

DbContext缓存提交数据的原因及Blazor Server下EF Core异常问题咨询

问题分析与解决方案

先帮你拆解遇到的几个核心问题,以及背后的底层原因:

1. 为什么user1会被重复插入,db.User.Local.Count还持续增加?

核心问题出在服务生命周期不匹配:

  • 你把IUserRepository注册成了Singleton(单例),这意味着整个应用生命周期里只有一个UserRepository实例。
  • 而AppContext(DbContext)注册成了Transient,但因为仓储是单例,它构造函数里注入的AppContext会被永久持有——第一次注入的那个DbContext会一直被复用,不会每次操作都创建新的。

当第一次添加user1失败时,这个实体已经被DbContext的ChangeTracker标记为Added状态并跟踪了。即使你后来尝试添加user2,调用SaveChangesAsync时,ChangeTracker里所有被跟踪的实体(包括失败的user1)都会被一起提交到数据库,导致重复插入。db.User.Local.Count持续增加,正是因为这些失败的实体一直被ChangeTracker缓存着。

另外,你的AddAsync方法吞了异常(catch (Exception e) { }),这会导致调用方完全不知道操作失败,无法及时处理错误状态,进一步加剧了问题。

2. 为什么编辑后查询仍显示旧数据?

这是EF Core ChangeTracker的默认优化行为:它会缓存已经查询过的实体实例。当你第一次查询某个用户后,ChangeTracker会保留这个实例;后续再查询同一用户时,EF Core会直接从缓存返回这个实例,而不是去数据库拉取最新数据——这是为了减少数据库查询次数,提升应用性能。

3. DbContext为什么会缓存提交的数据?

EF Core的ChangeTracker是其核心组件,它的主要作用是:

  • 跟踪实体的状态变化(新增、修改、删除)
  • 生成高效的SQL语句,只对真正变化的字段执行更新
  • 避免重复加载相同实体,降低数据库压力

缓存(跟踪)实体是实现这些功能的基础——只有当实体被跟踪时,EF Core才能准确识别哪些字段被修改了,从而生成精准的更新语句。


解决方案

调整服务生命周期(最关键)

Blazor Server中,每个SignalR连接对应一个Scoped生命周期范围。你应该把仓储和DbContext都注册为Scoped:

// Startup.cs 或 Program.cs(.NET 6+)
services.AddScoped<IUserRepository, UserRepository>();
services.AddDbContext<AppContext>(options => 
    options.UseSqlServer("你的数据库连接字符串"));
// 注:AddDbContext默认就是Scoped生命周期,无需额外指定Transient

这样每个用户连接会有独立的仓储和DbContext实例,操作之间不会互相干扰,ChangeTracker的状态也不会跨请求残留。

修复UserRepository的代码问题

不要吞异常,并且确保异步方法正确使用await:

public class UserRepository : IUserRepository
{
    private readonly AppContext _db;

    public UserRepository(AppContext context) 
    { 
        _db = context; 
    }

    public async Task AddAsync(User user, CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        try
        {
            await _db.User.AddAsync(user, cancellationToken);
            await _db.SaveChangesAsync(cancellationToken);
        }
        catch (Exception e)
        {
            // 将失败的实体从ChangeTracker中移除,避免后续提交重复插入
            _db.Entry(user).State = EntityState.Detached;
            // 不要吞异常,向上抛出让调用方处理错误
            throw;
        }
    }
}

获取最新数据的几种方式

如果需要绕过缓存获取数据库最新状态,可以用以下方法:

  • 查询时禁用跟踪:适合只读场景
    var latestUser = await _db.User.AsNoTracking().FirstOrDefaultAsync(u => u.Id == userId);
    
  • 刷新已跟踪的实体:如果已有缓存实体,强制从数据库重新加载
    var existingUser = await _db.User.FindAsync(userId);
    await _db.Entry(existingUser).ReloadAsync();
    
  • 清空ChangeTracker:一次性清除所有缓存实体(谨慎使用,会丢失未提交的状态)
    _db.ChangeTracker.Clear();
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 06:52:40