.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
相关产品推荐
相关产品推荐

