我正面临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调用被放置在所有必要的部分,但不确定我错过了什么,或者在async
await
和ConfigureAwait(false);
的组合中做错了什么。
2条答案
按热度按时间cngwdvgl1#
Blazor服务器端在注册和使用dbContexts时,由于组件的生命周期和处置而存在问题。简单地说,在这种情况下, transient 上下文并不是真正的 transient 。
最简单的解决方案是注入一个dbContextFactory,并为每个请求创建新的上下文- dbContext是一个非常轻量级的对象,所以没有问题。
我使用下面的代码来注册工厂和标准的dbContext:
在代码的configureservices部分执行此操作后
只需将
IDbContextFactory
注入到您组件中。只需记住,您必须手动处理此工厂创建的上下文(通过.Dispose
处理或在处理此类上下文时使用Using
关键字)。s8vozzvw2#
@quain提供的答案是正确的,它指出
IDBContextFactory
.Blazor本质上是异步的,因此尝试为所有数据库活动使用单个DBContext不会太久。但是,您的代码提出了以下几点:
1.您永远不应该将使用非托管资源(如DbContext)的服务用作 transient 。即使您实现了
Dispose
,它们也不会在SPA会话结束之前被释放。您将创建内存泄漏并耗尽资源。这篇MSDocs文章提供了有关
IDbContextFactory
的信息-https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/#using-a-dbcontext-factory-eg-for-blazor。1.如果您希望使服务的生命周期与页面/表单/组件相匹配[良好实践],本文(我的)将讨论如何做到这一点,并解决 transient 处置问题。