C# LINQ查询报non-static method requires a target异常排查
问题说明
使用C#开发数据查询功能时遇到运行时异常:System.Reflection.TargetException: 'Non-static method requires a target.'
初步排查确认异常直接触发原因是Scheme对象为null,已在LINQ的Where()子句中添加scheme空值判断做兼容处理,但代码运行后仍然抛出相同异常,业务实现代码如下:
ViewBag.LibraryTypeVersionID = (from ltv in _db.LibraryTypeVersions let schemeLibraries = _db.SchemeLibraryTypes .Where(x=> (scheme == null || x.IdScheme == scheme.IdScheme)) .Select(x => x.IdLibraryTypeVersion).ToList() select new { LibraryName = ltv.LibraryType.LibraryName, IdLibraryTypeVersion = ltv.IdLibraryTypeVersion, Version = ltv.Version, SchemeIdLibraryVersion = schemeLibraries }).OrderByDescending(x => x.LibraryName) .ToGroupedSelectList( x => x.LibraryName, x => x.LibraryName + " - " + x.Version, x => x.IdLibraryTypeVersion, x => x.SchemeIdLibraryVersion.Contains(x.IdLibraryTypeVersion));
根因分析
- 你写的
scheme == null || x.IdScheme == scheme.IdScheme判断仅在LINQ to Object(内存中执行集合查询)场景下会触发短路逻辑:即scheme为null时不会执行后面的scheme.IdScheme属性访问。但当前代码是EF/EF Core的数据库查询,Where子句内的所有逻辑会被EF解析为表达式树再翻译成SQL,解析过程不会遵循C#的短路执行规则,解析到scheme.IdScheme时,scheme本身为null,反射获取属性值就会直接抛出目标异常,你写的空判断根本没机会执行。 let子句内直接调用.ToList()会触发N+1查询问题:主查询每遍历一条LibraryTypeVersions记录,就会单独执行一次SchemeLibraryTypes的数据库查询,性能极差,同时会放大闭包空值的触发概率。
修复方案
- 把scheme的空值判断逻辑移到数据库查询外部,提前提取需要的参数,避免EF解析表达式时访问null对象的属性,同时一次性查询出需要关联的版本ID,彻底解决N+1问题:
// 提前在查询外处理scheme空值,查询关联的库版本ID,避免EF翻译表达式时触发空引用 List<int> targetSchemeLibraryVersions = new List<int>(); if (scheme != null) { int targetSchemeId = scheme.IdScheme; targetSchemeLibraryVersions = _db.SchemeLibraryTypes .Where(x => x.IdScheme == targetSchemeId) .Select(x => x.IdLibraryTypeVersion) .ToList(); }
- 调整主查询逻辑,直接复用内存中已经查询好的版本ID列表,去掉
let子句内的重复查询,修改后的完整代码如下:
List<int> targetSchemeLibraryVersions = new List<int>(); if (scheme != null) { int targetSchemeId = scheme.IdScheme; targetSchemeLibraryVersions = _db.SchemeLibraryTypes .Where(x => x.IdScheme == targetSchemeId) .Select(x => x.IdLibraryTypeVersion) .ToList(); } ViewBag.LibraryTypeVersionID = (from ltv in _db.LibraryTypeVersions select new { LibraryName = ltv.LibraryType.LibraryName, IdLibraryTypeVersion = ltv.IdLibraryTypeVersion, Version = ltv.Version, SchemeIdLibraryVersion = targetSchemeLibraryVersions }).OrderByDescending(x => x.LibraryName) .ToGroupedSelectList( x => x.LibraryName, x => x.LibraryName + " - " + x.Version, x => x.IdLibraryTypeVersion, x => x.SchemeIdLibraryVersion.Contains(x.IdLibraryTypeVersion));
补充:如果使用EF6及更早版本,表达式树解析阶段不会处理闭包变量的空值短路,把空判断移到查询外、提前提取值类型参数是最稳妥的处理方式,同时还能大幅提升查询性能。
内容的提问来源于stack exchange,提问作者Nicholas Mciver
相关产品推荐
相关产品推荐

