提前调用ToList()后查询返回Null的技术问题咨询
我来帮你理清楚这个问题——你遇到的情况本质是ORM(比如Entity Framework这类框架)处理数据库查询和内存集合的逻辑差异导致的,咱们一步步看:
首先对比你的两段代码:
正常返回的代码
var query = (from s in context.PersonDetails.GetQueryable(x => x.Id == Id) select new PersonViewModel { Name = s.Person.Firstname, Number = s.Person.Number }).ToList();
返回Null的代码
var query = (from s in context.PersonDetails.GetQueryable(x => x.Id == Id).ToList() select new PersonViewModel { Name = s.Person.Firstname, Number = s.Person.Number }).ToList();
问题根源
第一段代码的逻辑:
GetQueryable返回的是IQueryable类型,这意味着整个LINQ查询(包括后面的select)会被ORM转换成SQL语句,直接在数据库层面执行。这时候ORM会自动处理关联的Person数据——要么在SQL里通过JOIN查询一起取出,要么在映射对象时触发延迟加载(如果上下文还在作用域内),所以s.Person能正常获取到值,最终返回的PersonViewModel是正常的。第二段代码的问题:你提前调用了
.ToList(),这会把PersonDetails的数据一次性从数据库加载到内存中,但这里有个关键遗漏:默认情况下,ORM的延迟加载会让关联的Person属性暂时不加载(只有当你实际访问这个属性时才会去数据库查询)。如果这时候你的数据库上下文(context)已经被释放或者不在当前作用域内,当你在内存中访问s.Person时,就无法连接数据库获取关联数据,自然得到Null,最终导致PersonViewModel的Name和Number都是Null(你说第一段正常返回,所以核心问题就是关联数据未加载)。
另外提个小细节:你写的.toList()是小写的,C#是大小写敏感的,实际代码里应该是.ToList(),不然会直接编译错误——这大概率是你输入时的笔误。
解决方案
方案一:提前包含关联数据(推荐)
在调用.ToList()之前,用Include方法告诉ORM要一起加载关联的Person数据,这样数据会一次性从数据库取出,后续在内存中访问s.Person就不会是Null了:
var query = (from s in context.PersonDetails.GetQueryable(x => x.Id == Id) .Include(x => x.Person) // 显式加载关联的Person数据 .ToList() select new PersonViewModel { Name = s.Person.Firstname, Number = s.Person.Number }).ToList();
方案二:确保上下文作用域覆盖关联属性访问
如果不想提前加载所有关联数据,那要保证在数据库上下文的有效作用域内完成所有对s.Person的访问。比如在using块内完成整个操作:
using(var context = new YourDbContext()) { var query = (from s in context.PersonDetails.GetQueryable(x => x.Id == Id).ToList() select new PersonViewModel { Name = s.Person.Firstname, Number = s.Person.Number }).ToList(); // 在这里使用query,此时上下文还未释放,延迟加载会生效 }
不过这种方式不如提前Include高效,因为会触发额外的数据库查询(N+1问题)。
内容的提问来源于stack exchange,提问作者user9875456

