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

.NET Core应用启动时依赖多次加载问题排查及解决咨询

问题描述

我开发了一个使用依赖注入的.NET Core应用,所有Provider均在Startup.cs中以services.AddTransient方式注册。某控制器注入了一个Provider,该Provider又依赖其他多个服务。目前遇到的问题是:其中一个依赖的Provider(ActualsImportProvider)的构造函数被调用8次,即该Provider被实例化8次。我排查了依赖链未发现原因,且启动时会创建多个与初始页面无关的依赖。

Startup.cs部分服务注册代码

services.AddTransient<IResourceProvider, ResourceProvider>();
services.AddTransient<IStaticListProvider, StaticListProvider>();
services.AddTransient<IPlanActivityLineItemProvider, PlanActivityLineItemProvider>();
services.AddTransient<IPlanRevisionProvider, PlanRevisionProvider>();
services.AddTransient<IProjectImportExportProvider, ProjectImportExportProvider>();
services.AddTransient<IExpandoParserProvider, ExpandoParseProvider>();
services.AddTransient<IProjectPlanProvider, ProjectPLanProvider>();
services.AddTransient<IActualsImportProvider, ActualsImportProvider>();
services.AddTransient<IReportProvider, ReportProvider>();
services.AddTransient<IProjectWorkFlowProvider, ProjectWorkflowProvider>();

ActualsImportProvider构造代码

private readonly IURCSRepo _repo;
private readonly IPlanRevisionProvider _planRevisionProvider;
private readonly IExcelFileAndSheetValidationProvider _excelImportValidationProvider;
private readonly IExcelExtensionsProvider _excelExtensionsProvider;
private readonly IEmailProvider _emailProvider;
private readonly string _rpmActuals = "Actuals";
private TableParser<ActualHoursLineItemImportModel> _rpmLineItemTableParser;
private List<RoleDescriptionModel> roleModels = new List<RoleDescriptionModel>();
private List<PlanResourceModel> resources = new List<PlanResourceModel>();
private List<ProjectPlanHeaderModel> projectPlanHeaders = new List<ProjectPlanHeaderModel>();
private List<ChargingStrategyModel> chargingStrategies = new List<ChargingStrategyModel>();
private List<HoursTypeModel> hoursTypes = new List<HoursTypeModel>();
private readonly UserManager<ApplicationUser> _userManager;

public ActualsImportProvider(IURCSRepo uRCSRepo, IPlanRevisionProvider planRevisionProvider, IExcelFileAndSheetValidationProvider excelFileAndSheetValidationProvider, IExcelExtensionsProvider excelExtensionsProvider, IEmailProvider emailProvider, UserManager<ApplicationUser> userManager)
{
    _repo = uRCSRepo;
    _planRevisionProvider = planRevisionProvider;
    _excelImportValidationProvider = excelFileAndSheetValidationProvider;
    _excelExtensionsProvider = excelExtensionsProvider;
    _rpmLineItemTableParser = new TableParser<ActualHoursLineItemImportModel>(_excelExtensionsProvider);
    _emailProvider = emailProvider;
    roleModels = _repo.GetAllRoleDescriptions(true).MakeRoleDescriptions();
    resources = _repo.GetAllActiveResources().MakePlanResources();
    projectPlanHeaders = _repo.GetAllProjectPlanHeaders().MakeProjectPlanHeaderList();
    chargingStrategies = _repo.GetAllChargingStrategies().MakeChargingStrategies();
    hoursTypes = _repo.GetAllHoursTypes(true).MakeHoursTypes();
    _userManager = userManager;
}

尝试将服务改为AddSingleton注册时,出现错误:

InvalidOperationException: Cannot consume scoped service 'XXXXXXXSystem.Data.Entities.XXXXXXXSystemContext' from singleton 'XXXXXXXSystem.Data.Interfaces.IXXXXRepo'.

目前发现应用启动时加载了10-20个Provider,而初始页面仅需2个,怀疑DI容器配置存在问题,考虑是否更换为Autofac。后续通过调用栈排查发现,控制器的服务过滤器调用了某个Provider,该Provider又依赖多个与初始页面无关的服务,怀疑是架构设计问题导致依赖链过复杂。请问该分析是否正确,如何解决依赖重复加载问题?


解决方案分析

你的分析完全正确,核心问题在于过滤器触发了不必要的依赖链加载,再加上Transient生命周期的特性(每次请求都会创建新实例),直接导致服务被多次实例化。以下是具体解决步骤:

1. 优化过滤器的依赖加载

  • 检查控制器绑定的服务过滤器,移除对非必要Provider的直接依赖。如果过滤器确实需要相关功能,改用惰性注入(Lazy),只有在实际使用时才触发服务实例化,避免启动阶段提前加载整个依赖链:
    // 过滤器中使用惰性注入示例
    private readonly Lazy<IActualsImportProvider> _actualsImportProvider;
    public YourFilter(Lazy<IActualsImportProvider> actualsImportProvider)
    {
        _actualsImportProvider = actualsImportProvider;
    }
    
    // 仅在需要时才实例化服务
    public void OnActionExecuting(ActionExecutingContext context)
    {
        if (/* 满足使用条件 */)
        {
            var provider = _actualsImportProvider.Value;
            // 执行业务操作
        }
    }
    

2. 调整服务生命周期

  • 不要直接将ActualsImportProvider改为Singleton:它依赖的IURCSRepo是作用域(Scoped)服务(依赖DbContext),而Singleton无法消费Scoped服务,这是.NET Core DI的生命周期约束,无法绕过。
  • 改为Scoped生命周期:将注册代码改为services.AddScoped<IActualsImportProvider, ActualsImportProvider>(),这样每个请求只会实例化一次,大幅减少重复创建的次数。
  • 提取静态数据为Singleton服务:ActualsImportProvider构造函数中加载的roleModels、resources等只读数据,可单独封装成Singleton服务,避免每次实例化都重复查询数据库:
    // 新建静态数据服务
    public interface IStaticDataProvider
    {
        List<RoleDescriptionModel> RoleModels { get; }
        List<PlanResourceModel> Resources { get; }
        // 其他静态数据属性
    }
    
    public class StaticDataProvider : IStaticDataProvider
    {
        public List<RoleDescriptionModel> RoleModels { get; }
        public List<PlanResourceModel> Resources { get; }
    
        public StaticDataProvider(IURCSRepo repo)
        {
            RoleModels = repo.GetAllRoleDescriptions(true).MakeRoleDescriptions();
            Resources = repo.GetAllActiveResources().MakePlanResources();
            // 加载其他静态数据
        }
    }
    
    // 注册为Singleton
    services.AddSingleton<IStaticDataProvider, StaticDataProvider>();
    
    之后在ActualsImportProvider中注入IStaticDataProvider,替换原来直接查询数据库的逻辑,减少构造函数的执行开销。

3. 简化依赖链与职责拆分

  • 拆分过大的Provider:ActualsImportProvider构造函数依赖过多服务,且在构造函数中执行大量数据库查询,违反了单一职责原则。可将其拆分为多个小Provider,比如Excel解析、数据验证、数据导入逻辑分开,每个Provider只负责单一功能,降低依赖链复杂度。
  • 避免在构造函数中执行业务逻辑:构造函数仅用于依赖注入,数据库查询、初始化操作应移到单独的方法(如InitializeAsync()),在实际需要使用时再调用。

4. 关于更换DI容器

当前场景下不需要急于更换为Autofac,.NET Core内置DI足够解决问题。如果后续有更复杂的DI需求(如属性注入、批量注册等),再考虑更换也不迟。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 10:25:14