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

Blazor Server .NET 6中使用DbContextFactory遇到的异常问题咨询

问题分析:Blazor Server中DbContextFactory引发的随机异常

问题描述

从视频及微软官方文档得知,Blazor Server中应使用DbContextFactory,直接使用DbContext会引发问题。但改用DbContextFactory后出现随机异常:数据库已完成更新,但后续操作执行时会报错,提示“应影响一行数据但实际未影响”。

相关代码

Program.cs

builder.Services.AddScoped<IMOIClientService, MOIClientsService>();

builder.Services.AddAuthorization();

builder.Services.AddHttpContextAccessor();

//builder.Services.AddScoped<IDialogService, DialogService>();

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContextFactory<MOIDBContext>(x => x.UseSqlServer(connectionString));

MOIClientService

public class MOIClientsService : IMOIClientService
{
    private readonly IDbContextFactory<MOIDBContext> _contextFactory;

    public MOIClientsService(IDbContextFactory<MOIDBContext> contextFactory)
    {
        _contextFactory = contextFactory;
    }

    public async Task<List<Client>> GetClients()
    {
        using MOIDBContext db = _contextFactory.CreateDbContext();
        {
            var clientList = await db.Client.Include(i => i.FileList).ToListAsync();
            return clientList;
        }
    }

    public void AddClient(Client client)
    {
        using MOIDBContext db = _contextFactory.CreateDbContext();
        {
            db.Add(client);
            db.SaveChanges();
        }
    }
}

问题根源与解决方案

这个问题既不是Entity Framework的原生问题,也不是DbContextFactory本身的问题,而是上下文实例隔离与实体跟踪不一致导致的:

  1. 上下文实例隔离引发的实体状态冲突
    每次通过DbContextFactory创建的都是全新的DbContext实例,不同实例的实体跟踪完全独立。比如用Context A修改并保存了实体,后续用Context B获取同一实体时,Context B中的实体处于未跟踪状态,此时若尝试修改保存,EF可能无法正确识别变更状态,或因数据库数据已被修改触发并发冲突。

  2. 同步数据库操作的潜在风险
    AddClient方法使用了同步的SaveChanges(),在Blazor Server的异步环境中,同步操作可能导致线程阻塞或上下文状态异常,建议改为异步版本await db.SaveChangesAsync();。

  3. 并发更新冲突
    报错提示“应影响一行但实际未影响”,大概率是并发更新导致:两个操作同时修改同一行数据,第一个操作成功更新后,第二个操作执行时,EF检测到数据版本不一致(若未使用并发令牌),就会抛出该异常。

具体修复步骤

  • 统一实体操作的上下文实例:修改实体时,确保从同一个DbContext实例中获取并修改,或显式附加实体并设置正确状态。示例:
    public async Task UpdateClient(int clientId, Client updatedClient)
    {
        using var db = _contextFactory.CreateDbContext();
        var existingClient = await db.Client.FindAsync(clientId);
        if (existingClient != null)
        {
            db.Entry(existingClient).CurrentValues.SetValues(updatedClient);
            await db.SaveChangesAsync();
        }
    }
    
  • 所有数据库操作改用异步方法:调整AddClient为异步:
    public async Task AddClient(Client client)
    {
        using MOIDBContext db = _contextFactory.CreateDbContext();
        {
            db.Add(client);
            await db.SaveChangesAsync();
        }
    }
    
  • 添加并发令牌:在实体类中添加[Timestamp]属性(对应SQL Server的rowversion类型),让EF自动处理并发冲突:
    public class Client
    {
        // 其他属性
        [Timestamp]
        public byte[] RowVersion { get; set; }
    }
    
  • 捕获并处理并发异常:在SaveChangesAsync时处理DbUpdateConcurrencyException:
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException ex)
    {
        foreach (var entry in ex.Entries)
        {
            var databaseValues = await entry.GetDatabaseValuesAsync();
            if (databaseValues == null)
            {
                throw new InvalidOperationException("该记录已被其他用户删除");
            }
            entry.OriginalValues.SetValues(databaseValues);
            // 可根据业务逻辑选择重新保存或提示用户
        }
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 01:32:39