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本身的问题,而是上下文实例隔离与实体跟踪不一致导致的:
上下文实例隔离引发的实体状态冲突
每次通过DbContextFactory创建的都是全新的DbContext实例,不同实例的实体跟踪完全独立。比如用Context A修改并保存了实体,后续用Context B获取同一实体时,Context B中的实体处于未跟踪状态,此时若尝试修改保存,EF可能无法正确识别变更状态,或因数据库数据已被修改触发并发冲突。同步数据库操作的潜在风险
AddClient方法使用了同步的SaveChanges(),在Blazor Server的异步环境中,同步操作可能导致线程阻塞或上下文状态异常,建议改为异步版本await db.SaveChangesAsync();。并发更新冲突
报错提示“应影响一行但实际未影响”,大概率是并发更新导致:两个操作同时修改同一行数据,第一个操作成功更新后,第二个操作执行时,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
相关产品推荐
相关产品推荐

