EF4 Code First上下文已释放异常排查及大集合高效分离方案
一、为什么关闭懒加载仍出现异常且集合为null?
咱们一步步拆解问题:
1. crazyTaskList为null的核心原因
看你的GetSpecificProjectTasks方法,查询语句完全没用到传入的projectIdInQuestion参数!也就是说,不管你传5还是其他值,代码都会返回所有关联了Project的Task。如果数据库里没有这类数据,taskList会是一个空列表(不是null),但如果查询过程中因为上下文生命周期问题抛出了异常,crazyTaskList就会保持初始的null值,后续foreach循环自然会报错。
先把查询逻辑修正,加上关键的过滤条件:
List<Task> taskList = _context.Tasks .Where(tsk => tsk.ParentID_Project == projectIdInQuestion) .ToList();
(你的手动join完全没必要,EF会通过外键关联自动处理,直接用Where过滤即可)
2. ObjectContext已释放的异常原因
你用了using (DataAccessLayer dal = new DataAccessLayer()),这意味着代码块结束时dal会被销毁。如果你的DataAccessLayer实现了IDisposable接口(大概率是,否则using块不会生效),那么在Dispose方法里应该会释放_context。
虽然你关闭了懒加载,但如果Task实体中存在virtual导航属性(比如关联的Project对象),并且你在foreach循环中不小心访问了这些属性(比如tsk.Project.Name),即使懒加载关闭,EF也会尝试从数据库加载关联数据——但此时_context已经被释放,就会抛出那个异常。
二、如何高效分离大量Task对象?
你现在逐个标记EntityState.Detached的方式效率极低,推荐两种更优方案:
方案1:使用AsNoTracking()(最推荐)
直接在查询时告诉EF不要跟踪这些实体,这样查询出来的对象本身就是分离状态的,不需要后续手动处理,性能最优:
public List<Task> GetSpecificProjectTasks(int projectIdInQuestion) { // 不需要手动开/关懒加载,AsNoTracking直接返回分离对象 List<Task> taskList = _context.Tasks .AsNoTracking() .Where(tsk => tsk.ParentID_Project == projectIdInQuestion) .ToList(); return taskList; }
这个方法从根源上避免了跟踪实体,既高效又简洁。
方案2:批量分离已跟踪实体
如果因为某些原因必须先跟踪再分离,可以通过上下文的状态管理器批量处理,比遍历列表逐个标记高效:
public List<Task> GetSpecificProjectTasks(int projectIdInQuestion) { _context.Configuration.LazyLoadingEnabled = false; List<Task> taskList = _context.Tasks .Where(tsk => tsk.ParentID_Project == projectIdInQuestion) .ToList(); // 转换为ObjectContext批量分离Task实体 var objectContext = ((IObjectContextAdapter)_context).ObjectContext; var taskEntries = objectContext.ObjectStateManager .GetObjectStateEntries(EntityState.Unchanged) .Where(e => e.Entity is Task); foreach (var entry in taskEntries) { entry.ChangeState(EntityState.Detached); } _context.Configuration.LazyLoadingEnabled = true; return taskList; }
这种方式直接从EF的状态管理器中获取所有Task类型的条目,批量修改状态,比遍历列表逐个操作要高效很多。
内容的提问来源于stack exchange,提问作者crazyTech

