ASP.NET Core中如何在Service类用Session实现EF Core全局过滤器?
首先咱们拆解你遇到的两个核心问题:一是调用GetStructurePath()时抛出的Session未配置异常,二是如何在不让DbContext直接依赖HttpContext的前提下,实现动态全局过滤器。
一、修复Session未配置异常
你当前Configure方法的中间件顺序有问题——app.UseSession()必须放在app.UseMvc()之前,否则Mvc请求无法访问Session;同时要确保它在app.UseAuthentication()之后(因为Session通常依赖已认证的用户上下文)。
修改后的Configure方法代码:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IDbInitializer dbInitializer) { ApplicatonSettings.ConnectionString = Configuration.GetConnectionString("DefaultConnection"); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseDatabaseErrorPage(); } else { //app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseRequestLocalization(); app.UseAuthentication(); app.UseSession(); // 移到UseMvc之前,确保Mvc请求能访问Session dbInitializer.Initialize(); // 启动时初始化数据库,此时无请求上下文,Session值为null是正常的 app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
另外你注册IHttpContextAccessor的方式是正确的(ASP.NET Core 2.0需要手动注册),这部分无需改动。
二、优化EF Core全局过滤器实现(避免DbContext依赖HttpContext)
你当前在DbContext构造函数中获取Session值的方式存在隐患:DbContext在请求初期就会被实例化,此时Session可能还未被设置(比如Login动作是在后续请求中写入Session的),而且会导致DbContext间接依赖HttpContext,不符合分层设计原则。
推荐方案:在Service层实现动态过滤
放弃DbContext的全局过滤器,转而在业务Service层中根据Session值动态添加过滤条件。这种方式更灵活,也能保持DbContext的独立性。
- 先封装一个Scoped服务专门获取StructurePath(功能单一,职责更清晰):
public interface IStructurePathProvider { string GetCurrentStructurePath(); } public class StructurePathProvider : IStructurePathProvider { private readonly IHttpContextAccessor _contextAccessor; public StructurePathProvider(IHttpContextAccessor contextAccessor) { _contextAccessor = contextAccessor; } public string GetCurrentStructurePath() { return _contextAccessor.HttpContext?.Session?.GetString("StructurePath"); } }
- 在
ConfigureServices中注册这个服务:
services.AddScoped<IStructurePathProvider, StructurePathProvider>();
- 在你的业务Service中使用这个Provider添加过滤条件,比如
PersonService:
public class PersonService { private readonly ApplicationDbContext _dbContext; private readonly IStructurePathProvider _pathProvider; public PersonService(ApplicationDbContext dbContext, IStructurePathProvider pathProvider) { _dbContext = dbContext; _pathProvider = pathProvider; } public IEnumerable<PersonStruct> GetFilteredPersons() { var structurePath = _pathProvider.GetCurrentStructurePath(); if (!string.IsNullOrEmpty(structurePath)) { return _dbContext.PersonStruct.Where(p => p.Hierarchy.StartsWith(structurePath)); } // 如果没有Session值,返回全部数据 return _dbContext.PersonStruct; } }
备选方案:动态全局过滤器(如果必须用DbContext全局过滤)
如果你坚持要使用EF Core的全局过滤器,可以通过Scoped服务动态更新过滤参数。EF Core 2.0的全局过滤器是静态的,但我们可以通过在DbContext中定义一个可变属性,每次请求时更新它:
修改你的ApplicationDbContext:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> { private readonly IStructurePathProvider _pathProvider; // 用于动态存储当前请求的StructurePath public string CurrentStructurePath { get; private set; } public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, IStructurePathProvider pathProvider) : base(options) { _pathProvider = pathProvider; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 定义全局过滤器,使用CurrentStructurePath作为动态参数 modelBuilder.Entity<PersonStruct>().HasQueryFilter(p => string.IsNullOrEmpty(CurrentStructurePath) || p.Hierarchy.StartsWith(CurrentStructurePath)); } // 在查询前更新CurrentStructurePath public override DbSet<TEntity> Set<TEntity>() { CurrentStructurePath = _pathProvider.GetCurrentStructurePath(); return base.Set<TEntity>(); } // 在保存数据前更新CurrentStructurePath public override int SaveChanges(bool acceptAllChangesOnSuccess) { CurrentStructurePath = _pathProvider.GetCurrentStructurePath(); return base.SaveChanges(acceptAllChangesOnSuccess); } }
这种方式下,每个请求的DbContext实例都会在查询或保存前更新CurrentStructurePath,全局过滤器就能正确使用当前Session的值了。
关键注意点
- 绝对不要在DbContext构造函数中尝试获取Session:构造函数在请求初期执行,此时Session可能还未被初始化,必然会抛出异常。
- 保持DbContext的纯洁性:DbContext只负责数据访问,不应该依赖HttpContext这类Web层的对象,这样你的数据访问层才能复用在非Web场景中。
内容的提问来源于stack exchange,提问作者Ammar Bashir

