LINQ 查询不是“用不用”的问题,而是“怎么用对、用稳、用得明白”的问题。它本质是一套统一的查询语法和方法集,核心在于理解
IEnumerable<t></t>和
IQueryable<t></t>的区别,以及何时该用方法语法、何时该用查询表达式。
什么时候该用 Where
而不是 First
或 Single
这是最常混淆的起点:三者都做筛选,但语义和异常行为完全不同。
Where返回所有匹配项(
IEnumerable<t></t>),安全、可链式调用,适合后续继续处理
First返回第一个匹配项,没找到抛
InvalidOperationException;加
OrDefault版本(
FirstOrDefault)则返回
default(T)(如
null或
0)
Single要求**有且仅有一个**匹配项,多于一个或零个都抛异常;
SingleOrDefault仅在零个时返回默认值,多个仍报错
典型误用:用
Single查用户列表中邮箱匹配的记录——一旦数据库有脏数据(重复邮箱),线上直接崩溃。应优先用
FirstOrDefault,再显式判断是否为
null。
IQueryable<t></t>
和 IEnumerable<t></t>
混用导致 N+1 查询
EF Core / NHibernate 等 ORM 中,
IQueryable<t></t>是“可翻译的表达式树”,而
IEnumerable<t></t>是“内存集合”。一旦调用
ToList()、
ToArray()或任何强制执行的方法,查询就落地了。 错误写法:
var users = context.Users.ToList(); // 已加载全部用户到内存 var activeOrders = users.Where(u => u.IsActive).SelectMany(u => u.Orders).ToList(); // N+1:每用户查一次 Orders正确写法:
var activeOrders = context.Users
.Where(u => u.IsActive)
.SelectMany(u => u.Orders)
.ToList(); // 整个表达式由 EF 转成一条 SQL
关键判断点:看变量声明类型。如果变量是
IQueryable<t></t>,且没被中间调用过
AsEnumerable()或强制执行方法,那它还在“可翻译”状态。
查询表达式 vs 方法语法:选哪个?
两者编译后完全等价,选择只取决于可读性和场景。
多表 join、group by、let 临时变量时,查询表达式更贴近 SQL 思维,例如:var query = from u in context.Users
join o in context.Orders on u.Id equals o.UserId into userOrders
from o in userOrders.DefaultIfEmpty()
select new { u.Name, OrderCount = userOrders.Count() };
简单链式过滤、转换、聚合(Count、
Average、
Any)用方法语法更直接,例如:
bool hasPremiumUser = context.Users.Any(u => u.Tier == "Premium");混合使用常见且合法:先用查询表达式做复杂结构,再用方法语法收尾,如
query.OrderBy(x => x.Name).Take(10)
注意:
let在方法语法中对应的是
Select+ 匿名类型或元组,但可读性会下降,不推荐强行转。
延迟执行陷阱:Count()
和 Count
属性的区别
IEnumerable<t></t>没有
Count属性,只有
Count()方法;而
IQueryable<t></t>的
Count()会被翻译成
SELECT COUNT(*),但
Count属性(如
List<t>.Count</t>)是 O(1) 内存访问。 错误:对
IQueryable<t></t>反复调用
Count()—— 每次都发一次 COUNT 查询 正确:需要多次用数量时,先
var count = query.Count();缓存结果;若还需数据,再
var list = query.ToList();特别注意:不要写
if (query.Count() > 0),改用
query.Any(),后者生成
EXISTS,性能高得多
真正容易被忽略的,是把
IQueryable<t></t>传给多个方法后,在每个方法里都无意识触发了执行——比如日志、验证、分页计算,全都各自发了一次 SQL。
