Blazor Server端的.Net Core DbContext线程问题

wixjitnu  于 2023-04-08  发布在  .NET
关注(0)|答案(2)|浏览(190)

我正面临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调用被放置在所有必要的部分,但不确定我错过了什么,或者在asyncawaitConfigureAwait(false);的组合中做错了什么。

cngwdvgl

cngwdvgl1#

Blazor服务器端在注册和使用dbContexts时,由于组件的生命周期和处置而存在问题。简单地说,在这种情况下, transient 上下文并不是真正的 transient 。
最简单的解决方案是注入一个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);

在代码的configureservices部分执行此操作后
只需将IDbContextFactory注入到您组件中。只需记住,您必须手动处理此工厂创建的上下文(通过.Dispose处理或在处理此类上下文时使用Using关键字)。

s8vozzvw

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 处置问题。

相关问题