You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor应用中DbContextFactory引发Entity Framework Core性能问题求助

问题

将应用从单一DbContext改为DbContextFactory后功能正常,但性能明显下降,导致整个应用出现输入延迟,而使用长生命周期数据上下文时无此问题。是否有人遇到过类似问题,并有解决办法?

注册代码如下:

builder.Services.AddDbContextFactory<DBContext>(
    options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DevConnection")),
    ServiceLifetime.Scoped);

补充背景:我知道在Blazor中使用Entity Framework Core时,DbContextFactory是调用DbContext的正确方式。最初所有数据都从单一作用域DbContext实例获取,为解决并发问题,我改成每次工作单元创建新上下文,使用后释放:

DBContext _ContextInstance = _DBContextFactory.CreateDbContext();
ContextInstance.Dispose();

默认情况下DbContextFactory是单例服务,我把它改成了Scoped以适配其他Scoped服务。奇怪的是,本地运行时修改前一切正常,修改后出现了作用域服务尝试使用单例服务的错误。

以下是一个运行缓慢的方法示例(本不应花费3-4秒,VS2022调试器中本地运行速度快一倍):

public List<VMRAMarginDetail> GetFilteredProjects(FilterRef FilterRef, List<int> ProjectIds)
{
    if (ProjectIds != null)
    {
        List<VMRAMarginDetail> FilteredProjects = new();
        var dateFilterValue = Convert.ToDateTime(FilterRef.MonthEnding);

        using (MarginReportAppContext _MRAInstance = _MRAFactory.CreateDbContext())
        {
            FilteredProjects = _MRAInstance.V_MRA_MarginDetails.Where(a => a.MonthEnding == dateFilterValue && ProjectIds.Contains(a.ProjectId)).ToList();
        }

        if (FilterRef.Region != null && FilterRef.Region.Length > 0)
        {
            //按区域筛选
            FilteredProjects = FilteredProjects.Where(a => a.Region.ToUpper() == FilterRef.Region.ToUpper() && a.Group.ToUpper() == FilterRef.Group.ToUpper() && a.Division.ToUpper() == FilterRef.Division).ToList();
        }
        else if (FilterRef.Group != null && FilterRef.Group.Length > 0)
        {
            //按组筛选
            FilteredProjects = FilteredProjects.Where(a => a.Group.ToUpper() == FilterRef.Group.ToUpper() && a.Division.ToUpper() == FilterRef.Division).ToList();
        }
        else if (FilterRef.Division != null && FilterRef.Division.Length > 0)
        {
            //按部门筛选
            FilteredProjects = FilteredProjects.Where(a => a.Division.ToUpper() == FilterRef.Division).ToList();
        }

        if (FilterRef.MprApprover != null && FilterRef.MprApprover.Length > 0)
        {
            //按MPR审批人筛选
            FilteredProjects = FilteredProjects.Where(a => a.MPRApprover == FilterRef.MprApprover).ToList();
        }

        if (FilterRef.Company != null && FilterRef.Company.Length > 0)
        {
            //按公司筛选
            FilteredProjects = FilteredProjects.Where(a => a.CompanyName == FilterRef.Company).ToList();
        }

        return FilteredProjects;
    }
    else
    {
        return new();
    }
}
解决建议

一、性能问题优化

  1. 将内存筛选移至数据库层执行
    当前代码先把符合日期和ProjectIds的数据全查出来,再在内存中做后续筛选,这会加载大量不必要的数据到内存,数据量较大时性能极差。应该把所有筛选条件合并到EF查询中,让SQL Server完成筛选:

    using (MarginReportAppContext _MRAInstance = _MRAFactory.CreateDbContext())
    {
        var query = _MRAInstance.V_MRA_MarginDetails.Where(a => a.MonthEnding == dateFilterValue && ProjectIds.Contains(a.ProjectId));
    
        if (FilterRef.Region != null && FilterRef.Region.Length > 0)
        {
            query = query.Where(a => a.Region.ToUpper() == FilterRef.Region.ToUpper() && a.Group.ToUpper() == FilterRef.Group.ToUpper() && a.Division.ToUpper() == FilterRef.Division);
        }
        else if (FilterRef.Group != null && FilterRef.Group.Length > 0)
        {
            query = query.Where(a => a.Group.ToUpper() == FilterRef.Group.ToUpper() && a.Division.ToUpper() == FilterRef.Division);
        }
        else if (FilterRef.Division != null && FilterRef.Division.Length > 0)
        {
            query = query.Where(a => a.Division.ToUpper() == FilterRef.Division);
        }
    
        if (FilterRef.MprApprover != null && FilterRef.MprApprover.Length > 0)
        {
            query = query.Where(a => a.MPRApprover == FilterRef.MprApprover);
        }
    
        if (FilterRef.Company != null && FilterRef.Company.Length > 0)
        {
            query = query.Where(a => a.CompanyName == FilterRef.Company);
        }
    
        FilteredProjects = query.ToList();
    }
    

    这样EF会生成包含所有筛选条件的SQL,只返回需要的数据,大幅减少数据传输和内存占用。

  2. 优化ProjectIds.Contains的性能
    当ProjectIds数量较多时,Contains会生成IN子句,SQL Server处理大量IN值时性能会下降。可以考虑:

    • 如果ProjectIds数量超过几百条,改用临时表或表值参数传递给SQL
    • 确保V_MRA_MarginDetails表的ProjectId字段有索引
  3. 按需复用DbContext实例
    虽然Blazor中不建议长生命周期DbContext,但不必每次数据库操作都创建新实例。同一请求/作用域内的多个数据库操作,可复用同一个DbContext实例,减少连接初始化开销。

二、作用域服务错误修复

  • 恢复DbContextFactory的默认Singleton生命周期
    DbContextFactory默认配置为Singleton是EF的推荐方案。出现作用域服务依赖单例的错误,大概率是你的服务依赖链存在问题:比如有Singleton服务注入了Scoped服务。检查所有服务的注册生命周期,确保依赖关系合规。
  • 坚持用using语句管理DbContext生命周期
    用using创建并释放DbContext是正确的方式,能避免上下文泄漏,保证资源正常回收。

内容的提问来源于stack exchange,提问作者Avery

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 02:45:18