我面临来自 Blazor 服务器端 .NET Core 项目的线程问题。
DbContext
定义如下所示:
public class ADPortalDbContext:DbContext
{
public DbSet<Company> Companies { get; set; }
public ADPortalDbContext(DbContextOptions<ADPortalDbContext> options)
: base(options) { }
}
从数据库返回结果的
Service
如下所示:
public class CompanyService : ICompanyService
{
private readonly ADPortalDbContext context;
public CompanyService(ADPortalDbContext context)
{
this.context = context;
}
public async Task<IEnumerable<Company>> GetCompaniesSearchText(string searchText)
{
try
{
return await context.Companies
.Where(i => EF.Functions.Like(i.Name.ToLower(), $"%{searchText.ToLower()}%"))
.ToListAsync()
.ConfigureAwait(false);
}
catch(Exception ex)
{
throw new InvalidOperationException("Unable to return results " + ex.Message);
}
}
}
当我多次调用同一个函数时,异常发生在
GetCompaniesSearchText
方法错误消息如下所示[调用此函数以填充自动完成下拉列表的结果,用户可以继续在下拉列表中键入字符这将继续被执行并随机以异常结束]
无法返回结果 在此开始了第二次操作 上一个操作完成之前的上下文。这通常是由于 不同线程同时使用同一个实例 DbContext.有关如何避免线程问题的更多信息 DbContext,参见https://go.microsoft.com/fwlink/?linkid=2097913.
Blazor 应用程序的 Startup.cs 如下所示:
services.AddDbContext<ADPortalDbContext>(options =>
options.UseMySql(connStr, srvVersion, x =>
{
x.MigrationsAssembly("DCPortal.Infrastructre");
}),
contextLifetime: ServiceLifetime.Transient);
调用服务的 Blazor 页面如下:
@inject ICompanyService CompanyService
/// <summary>
/// Autocomplete search handler
/// Returns the matched companies for the given search text
/// </summary>
/// <param name="searchText"></param>
/// <returns></returns>
private async Task<IEnumerable<Company>> SearchCompanies(string searchText)
{
try
{
IEnumerable<Company> companies_ListDb = await CompanyService.GetCompaniesSearchText(searchText);
List<Company> CompaniesList = companies_ListDb.ToList();
return companies_ListDb;
}
catch(Exception ex)
{
return Enumerable.Empty<Company>();
}
}
看起来异步等待调用被放置在所有必要的部分,但不确定我错过了什么或做错了
async
await
和ConfigureAwait(false);
的组合。
Blazor 服务器端在注册和使用 dbContexts 时由于组件的生命周期和处理而存在问题。简而言之——瞬态上下文在这种情况下并不是真正的瞬态。
最简单的解决方案是注入一个 dbContextFactory 并为每个请求创建新的上下文——dbContext 是一个非常轻量级的对象,所以没有问题。
我使用下面的代码来注册工厂和标准 dbContext:
var sqlConfiguration = new Action<DbContextOptionsBuilder>(options =>
{
if (isDevelopment)
options.EnableSensitiveDataLogging();
options.UseSqlServer(configuration.GetConnectionString(DbConnectionStringName),
sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
});
});
services.AddDbContextFactory<ApplicationDbContext>(options => sqlConfiguration(options));
services.AddDbContext<ApplicationDbContext>(options => sqlConfiguration(options), optionsLifetime: ServiceLifetime.Singleton);
在代码的配置服务部分这样做之后
只需将
IDbContextFactory
注入您的组件即可。请记住,您必须手动处置由该工厂创建的上下文(通过 .Dispose
处置或在处理此类上下文时仅使用 Using
关键字。)
@quain 提供的答案是正确的,将您指向
IDBContextFactory
。 Blazor 本质上是异步的,因此尝试对所有数据库活动使用单个 DBContext 不会持续很长时间。
但是,您的代码提出了几点:
Dispose
,它们也不会在SPA 会话结束前被处理掉。您将造成内存泄漏,并耗尽资源。这篇 MSDocs 文章提供了有关
IDbContextFactory
的信息 - https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/#using-a-dbcontext-factory-eg-for-blazor。