Avalonia应用EF Core并发调用DbContext报错求助
问题根源确认
你遇到的错误确实是同一DbContext实例被多线程并发访问导致的。Entity Framework Core的DbContext设计为非线程安全的,即使是Scoped注册,在同一个作用域内获取的是同一个实例,用Task.WhenAll并发执行两个查询时,多个线程同时操作这个实例,就会抛出InvalidOperationException。
解决方案1:为每个并发操作创建独立的DbContext作用域
既然Scoped服务在同一个作用域内是单例,可手动为每个查询创建新作用域,确保每个查询使用独立的DbContext:
// 在UserService中注入IServiceScopeFactory private readonly IServiceScopeFactory _scopeFactory; public UserService(IServiceScopeFactory scopeFactory) { _scopeFactory = scopeFactory; } public async Task<(bool UsernameExists, bool EmailExists)> CheckDuplicatesAsync(string username, string email) { // 为用户名检查创建独立作用域 var usernameTask = Task.Run(async () => { using var scope = _scopeFactory.CreateScope(); var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>(); return await repo.IsUsernameExistsAsync(username); }); // 为邮箱检查创建独立作用域 var emailTask = Task.Run(async () => { using var scope = _scopeFactory.CreateScope(); var repo = scope.ServiceProvider.GetRequiredService<IUserRepository>(); return await repo.IsEmailExistsAsync(email); }); await Task.WhenAll(usernameTask, emailTask); return (usernameTask.Result, emailTask.Result); }
解决方案2:合并查询为单次数据库调用
把两个存在性检查合并成一次数据库请求,既避免并发问题,又减少数据库往返次数提升性能:
// 在IUserRepository中添加合并查询方法 public async Task<(bool UsernameExists, bool EmailExists)> CheckUsernameAndEmailExistsAsync(string username, string email) { var result = await _context.Users .Where(u => u.Username == username || u.Email == email) .Select(u => new { IsUsernameMatch = u.Username == username, IsEmailMatch = u.Email == email }) .ToListAsync(); bool usernameExists = result.Any(r => r.IsUsernameMatch); bool emailExists = result.Any(r => r.IsEmailMatch); return (usernameExists, emailExists); }
之后直接调用该方法即可,无需并发执行。
解决方案3:使用DbContextFactory(EF Core 5+)
如果使用EF Core 5及以上版本,可注册IDbContextFactory<DatabaseContext>替代直接注册DbContext,每次获取的都是新的DbContext实例:
// 在Program.cs中注册服务 builder.Services.AddDbContextFactory<DatabaseContext>(options => options.UseNpgsql("你的数据库连接字符串"));
在Repository中注入IDbContextFactory<DatabaseContext>,每次查询时创建新实例:
private readonly IDbContextFactory<DatabaseContext> _contextFactory; public UserRepository(IDbContextFactory<DatabaseContext> contextFactory) { _contextFactory = contextFactory; } public async Task<bool> IsUsernameExistsAsync(string username) { using var context = _contextFactory.CreateDbContext(); return await context.Users.AnyAsync(u => u.Username == username); } public async Task<bool> IsEmailExistsAsync(string email) { using var context = _contextFactory.CreateDbContext(); return await context.Users.AnyAsync(u => u.Email == email); }
此时用Task.WhenAll并发调用两个方法时,每个方法都会使用独立的DbContext,不会触发线程安全问题。
内容的提问来源于stack exchange,提问作者solodeving
相关产品推荐
相关产品推荐

