ASP.Net MVC控制器构造函数Session为空错误及客户全局过滤方案咨询
嘿,我来帮你理清这个问题哈!首先你遇到的Session.get returned null错误,原因很明确——在ASP.NET MVC控制器的构造函数执行时,Session对象还没被框架初始化呢!这时候去拿Session里的值肯定拿不到。
那要实现「仅在单一位置过滤db.Customers,让控制器其余部分直接使用」的需求,有几个靠谱的方案,我给你拆解一下:
方案一:重写控制器的Initialize方法
控制器的Initialize方法是在Session已经可用之后才会执行的,刚好适合在这里做过滤操作。你可以这样修改代码:
public class SOSRController : Controller { private BusinessContext db; protected override void Initialize(RequestContext requestContext) { base.Initialize(requestContext); db = new BusinessContext(); // 现在Session已经能正常访问了 Employee employee = (Employee)Session["CurrentUser"]; // 注意:直接强制转换DbSet可能有问题,建议改用IQueryable存储过滤结果 // 可以给控制器新增一个属性:public IQueryable<Customer> FilteredCustomers { get; private set; } // 然后赋值:FilteredCustomers = db.Customers.Where(x => x.Business.Id == employee.bizId); db.Customers = db.Customers.Where(x => x.Business.Id == employee.bizId) as DbSet<Customer>; } public ActionResult Index() { // 后续直接用过滤后的集合即可 List<Customer> LstCust= db.Customers.Where(x => x.Name.Contains("asd")).ToList(); return View(LstCust); } }
这个方案的好处是控制器级别的过滤逻辑集中在Initialize里,后续Action直接调用就行,完美避开了Session未初始化的问题。
方案二:给DbContext添加全局查询过滤器(推荐)
如果这个业务过滤逻辑是全局的(所有用到BusinessContext的地方都需要过滤当前业务的客户),那把过滤逻辑放到DbContext里会更优雅。EF6及以上(包括EF Core)都支持全局查询过滤器:
EF Core版本实现:
在你的BusinessContext中添加全局过滤规则:
public class BusinessContext : DbContext { public DbSet<Customer> Customers { get; set; } // 新增属性存储当前用户的业务ID public int CurrentBizId { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 添加全局查询过滤器,所有查询都会自动带上这个条件 modelBuilder.Entity<Customer>().HasQueryFilter(c => c.Business.Id == CurrentBizId); } }
然后在控制器的Initialize方法中设置当前业务ID:
public class SOSRController : Controller { private BusinessContext db; protected override void Initialize(RequestContext requestContext) { base.Initialize(requestContext); db = new BusinessContext(); Employee employee = (Employee)Session["CurrentUser"]; db.CurrentBizId = employee.bizId; } public ActionResult Index() { // 这里db.Customers会自动应用全局过滤条件 List<Customer> LstCust= db.Customers.Where(x => x.Name.Contains("asd")).ToList(); return View(LstCust); } }
EF6版本实现:
EF6没有原生的HasQueryFilter,但可以通过自定义DbSet包装类实现类似效果:
public class FilteredDbSet<T> : DbSet<T>, IQueryable<T> where T : class { private readonly DbSet<T> _innerSet; private readonly Expression<Func<T, bool>> _filter; public FilteredDbSet(DbSet<T> innerSet, Expression<Func<T, bool>> filter) { _innerSet = innerSet; _filter = filter; } public override IQueryable<T> AsQueryable() { return _innerSet.Where(_filter).AsQueryable(); } // 根据需求重写Add、Remove等其他方法 }
然后在BusinessContext中替换Customers的声明:
public class BusinessContext : DbContext { private DbSet<Customer> _customers; public int CurrentBizId { get; set; } public DbSet<Customer> Customers { get { return new FilteredDbSet<Customer>(_customers, c => c.Business.Id == CurrentBizId); } set { _customers = value; } } }
控制器的用法和EF Core版本一致,只需设置CurrentBizId即可。
这个方案的优势是过滤逻辑和数据上下文绑定,所有使用该DbContext的地方都会自动应用过滤,不用在每个控制器重复编写,维护起来更省心。
方案三:使用Action Filter
创建自定义Action Filter,在Action执行前完成过滤操作:
public class FilterCustomersAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { base.OnActionExecuting(filterContext); var controller = filterContext.Controller as SOSRController; if (controller != null) { Employee employee = (Employee)controller.Session["CurrentUser"]; controller.db.Customers = controller.db.Customers.Where(x => x.Business.Id == employee.bizId) as DbSet<Customer>; } } }
然后在控制器或特定Action上添加该特性:
[FilterCustomers] public class SOSRController : Controller { public BusinessContext db = new BusinessContext(); public ActionResult Index() { List<Customer> LstCust= db.Customers.Where(x => x.Name.Contains("asd")).ToList(); return View(LstCust); } }
这个方案适合仅在特定Action上应用过滤的场景,但控制器和过滤器的耦合度稍高。
总结一下:如果需要全局过滤,优先选方案二(全局查询过滤器);如果只是当前控制器需要过滤,**方案一(重写Initialize)**更直接高效。
内容的提问来源于stack exchange,提问作者Waqas Javaid

