将 IQueryable 对象当作”表”进行二次查询
核心概念
在 LINQ 中,IQueryable<T> 对象可以像数据库表一样被再次查询。你可以对它继续执行 where 过滤、join 连接等操作,LINQ to SQL 会将所有操作合并为一条 SQL 语句发送给数据库执行,而不是先查出来再在内存中过滤。
完整示例代码
using (var db = new DataClasses1DataContext())
{
// 第一步:表连接,得到一个 IQueryable 查询对象(尚未执行)
var query = from s in db.Student
join p in db.Product
on s.ID equals p.Sid
select new
{
s.ID,
s.Name,
s.ClassID,
Dname = p.Name,
p.Price
};
// 第二步:对 query 再次执行 where 过滤
var query2 = from q in query
where q.ID == 1
select q;
var result = query2.ToList();
// 第三步:对 query 再次执行 join 连接(与 Classx 表关联)
var query3 = from q in query
join c in db.Classx
on q.ClassID equals c.ID
select new { q, c };
var result2 = query3.ToList();
}Code language: JavaScript (javascript)
执行逻辑示意
┌──────────────────────────────────────────────────────────────────┐
│ query(连接后的中间结果集,类型为 IQueryable<匿名类型>) │
│ 相当于一张"虚拟表",包含学生 + 产品的联合字段 │
├──────────────────────────────────────────────────────────────────┤
│ 可以像操作实体表一样对它进行: │
│ ✦ where 过滤 ✦ join 连接 ✦ orderby 排序 │
│ ✦ group by 分组 ✦ select 投影 │
└──────────────────────────────────────────────────────────────────┘Code language: HTML, XML (xml)
| 步骤 | 变量 | 操作 | 说明 |
|---|---|---|---|
| ① | query | Student ⋈ Product | 内连接,投影出 ID、Name、ClassID、Dname、Price |
| ② | query2 | query + where ID == 1 | 基于虚拟表再过滤,SQL 合并为一条语句 |
| ③ | query3 | query ⋈ Classx | 基于虚拟表再与班级表连接,形成三层关联 |
关键要点
- 延迟执行(Deferred Execution):
query、query2、query3在定义时都不会立即查询数据库,只有在调用.ToList()/.First()/.Count()等终端操作符时才会执行- SQL 合并机制: LINQ to SQL 会将多层查询组合成单条 SQL。以
query2为例,最终生成的 SQL 等价于:SELECT s.ID, s.Name, s.ClassID, p.Name AS Dname, p.Price FROM Student s INNER JOIN Product p ON s.ID = p.Sid WHERE s.ID = 1- 复用性: 同一个
query可以被多个后续查询复用,各自追加不同条件,互不干扰
常见使用场景
| 场景 | 做法 |
|---|---|
| 公共查询条件复用 | 先定义基础 IQueryable,多处复用并追加不同 where |
| 动态条件拼接 | 根据业务判断逐步追加 Where()、OrderBy() 等 |
| 多表级联 | 先连接 A、B 表,再基于结果连接 C、D 表 |
注意事项
IQueryablevsIEnumerable: 如果在中间调用了.AsEnumerable()或.ToList(),后续操作将在内存中执行而非翻译成 SQL,可能导致全表加载,需谨慎使用。匿名类型的局限:
query中使用了匿名类型(new { s.ID, s.Name, ... }),只能在当前方法内使用,无法跨方法传递。如需跨方法复用,建议定义具名 DTO 类。
Previous: 分组合并行
Next: 分组后进行过滤having的使用