目 录CONTENT

文章目录

CSharp(五十一) LINQ 详解 —— 查询语法 vs 方法语法

C# LINQ 详解 —— 查询语法 vs 方法语法


一、什么是 LINQ?

LINQ(Language Integrated Query,语言集成查询)是 C# 中一套操作数据的统一语法。

你可以用几乎一样的方式去查询数组、集合、XML、数据库,而不用管数据从哪来。

打个比喻:

不用 LINQ:你要从 100 个学生里找分数 > 80 的人,要写 for 循环 + if + 临时列表。
用 LINQ:students.Where(s => s.Score > 80) —— 一行搞定。

LINQ 把"查询"变成了 C# 语言的一部分,像写 SQL 一样操作内存里的数据。


二、准备测试数据

以下所有示例都基于这份数据:

using System;
using System.Collections.Generic;
using System.Linq;

public class Student
{
    public string Name;
    public int Age;
    public int Score;
    public string City;
    public string Subject;

    public override string ToString()
    {
        return $"{Name,-6} | {Age}岁 | {City,-4} | {Subject,-4} | {Score}分";
    }
}

// 测试数据
List<Student> students = new List<Student>
{
    new Student { Name = "张三", Age = 18, Score = 92, City = "北京", Subject = "数学" },
    new Student { Name = "李四", Age = 19, Score = 85, City = "上海", Subject = "数学" },
    new Student { Name = "王五", Age = 18, Score = 76, City = "北京", Subject = "语文" },
    new Student { Name = "赵六", Age = 20, Score = 58, City = "广州", Subject = "数学" },
    new Student { Name = "孙七", Age = 19, Score = 88, City = "上海", Subject = "语文" },
    new Student { Name = "周八", Age = 20, Score = 95, City = "北京", Subject = "英语" },
    new Student { Name = "吴九", Age = 18, Score = 45, City = "广州", Subject = "英语" },
    new Student { Name = "郑十", Age = 19, Score = 72, City = "深圳", Subject = "数学" },
};

三、两种语法——同一个结果,两种写法

LINQ 有两种写法,写出来的结果一模一样

3.1 方法语法(Method Syntax)—— 链式调用

var result = students
    .Where(s => s.Score >= 60)
    .OrderByDescending(s => s.Score)
    .Select(s => s.Name);

像搭积木,一个方法接一个方法。每个方法接收一个 Lambda 表达式。

3.2 查询语法(Query Syntax)—— 像 SQL

var result = from s in students
             where s.Score >= 60
             orderby s.Score descending
             select s.Name;

像写 SQL 一样,用 fromwhereorderbyselect 关键字。

3.3 结果完全一样

// 两种写法跑出来的结果一样
foreach (var name in result)
    Console.WriteLine(name);

输出(两种写法相同):

周八
张三
孙七
李四
王五
郑十

四、查询语法 vs 方法语法 —— 逐句对照

下面把同一个操作,分别用两种语法写出来:

4.1 筛选(Where)

// 查询语法
var passed1 = from s in students
              where s.Score >= 60
              select s;

// 方法语法
var passed2 = students.Where(s => s.Score >= 60);

4.2 投影(Select)—— 只要名字

// 查询语法
var names1 = from s in students
             select s.Name;

// 方法语法
var names2 = students.Select(s => s.Name);

4.3 排序(OrderBy)

// 查询语法
var sorted1 = from s in students
              orderby s.Score descending
              select s;

// 方法语法
var sorted2 = students.OrderByDescending(s => s.Score);

4.4 多级排序(OrderBy + ThenBy)

// 查询语法
var multi1 = from s in students
             orderby s.Age, s.Score descending
             select s;

// 方法语法
var multi2 = students
    .OrderBy(s => s.Age)
    .ThenByDescending(s => s.Score);

4.5 分组(GroupBy)

// 查询语法
var groups1 = from s in students
              group s by s.City;

// 方法语法
var groups2 = students.GroupBy(s => s.City);

// 遍历
foreach (var g in groups1)
{
    Console.WriteLine($"--- {g.Key} ---");
    foreach (var s in g)
        Console.WriteLine($"  {s.Name}");
}

4.6 分组后投影(group by + into)

// 查询语法
var cityStats1 = from s in students
                 group s by s.City into g
                 select new
                 {
                     City = g.Key,
                     Count = g.Count(),
                     Avg = g.Average(s => s.Score)
                 };

// 方法语法
var cityStats2 = students
    .GroupBy(s => s.City)
    .Select(g => new
    {
        City = g.Key,
        Count = g.Count(),
        Avg = g.Average(s => s.Score)
    });

4.7 连接(Join)

var cityRatings = new List<CityRating>
{
    new CityRating { City = "北京", Rating = "一线" },
    new CityRating { City = "上海", Rating = "一线" },
    new CityRating { City = "广州", Rating = "一线" },
    new CityRating { City = "深圳", Rating = "一线" },
};

// 查询语法(像 SQL 的 INNER JOIN)
var joined1 = from s in students
              join c in cityRatings on s.City equals c.City
              select new { s.Name, s.City, c.Rating };

// 方法语法
var joined2 = students.Join(
    cityRatings,
    s => s.City,
    c => c.City,
    (s, c) => new { s.Name, s.City, c.Rating });

4.8 使用 let 关键字(查询语法独有)

let 可以在查询中创建一个中间变量,让后面的语句引用;方法语法中这个效果不明显。

// 查询语法——用 let 创建中间变量
var info1 = from s in students
            let isPassed = s.Score >= 60          // 创建一个中间变量
            let grade = s.Score >= 90 ? "A" : s.Score >= 80 ? "B" : s.Score >= 60 ? "C" : "D"
            select new { s.Name, s.Score, isPassed, grade };

// 方法语法——效果可以用匿名对象实现(或者用扩展的 Select)
var info2 = students.Select(s => new
{
    s.Name,
    s.Score,
    isPassed = s.Score >= 60,
    grade = s.Score >= 90 ? "A" : s.Score >= 80 ? "B" : s.Score >= 60 ? "C" : "D"
});

4.9 完整对比总表

操作 查询语法 方法语法
筛选 from s in list where 条件 select s list.Where(s => 条件)
投影 from s in list select s.Name list.Select(s => s.Name)
排序(升) from s in list orderby s.Age select s list.OrderBy(s => s.Age)
排序(降) from s in list orderby s.Age descending list.OrderByDescending(s => s.Age)
多级排序 orderby s.Age, s.Score descending .OrderBy().ThenByDescending()
分组 group s by s.City .GroupBy(s => s.City)
分组投影 group s by s.City into g select new { g.Key, Cnt = g.Count() } .GroupBy().Select(g => new { ... })
连接 join c in cities on s.City equals c.City .Join(cities, s=>s.City, c=>c.City, ...)
取前N (无直接语法) .Take(N)
跳过N (无直接语法) .Skip(N)
去重 (无直接语法) .Distinct()
求和 (无直接语法) .Sum(s => s.Score)
计数 (无直接语法) .Count()
是否有元素 (无直接语法) .Any()
第一个 (无直接语法) .First()

关键发现:查询语法本质是方法语法的"语法糖",编译器会把查询语法翻译成方法调用。但查询语法不支持 Take/Skip/Count/Sum 等操作,这些必须用方法语法。


五、方法语法详解

5.1 什么是方法语法?

方法语法就是调用 IEnumerable 上的扩展方法,用链式调用把多个操作串起来。

核心特点:每个方法接收一个 Lambda 表达式(或委托),返回一个新的 IEnumerable

var result = students        // 数据源
    .Where(s => s.Score >= 60)   // 筛选:接收 Func<Student, bool>
    .OrderByDescending(s => s.Score)  // 排序:接收 Func<Student, int>
    .Select(s => s.Name)           // 投影:接收 Func<Student, string>
    .ToList();                     // 立即执行:返回 List<string>

5.2 方法语法分类

筛选类

students.Where(s => s.Score >= 60)              // 按条件过滤
students.OfType<string>()                       // 按类型过滤(从 object[] 中)

投影类

students.Select(s => s.Name)                    // 转换成新形式
students.SelectMany(s => s.Courses)             // 扁平化嵌套集合

排序类

students.OrderBy(s => s.Age)                    // 升序
students.OrderByDescending(s => s.Age)          // 降序
students.OrderBy(s => s.Age).ThenBy(s => s.Score)  // 二级排序
students.Reverse()                              // 反转

分组类

students.GroupBy(s => s.City)                   // 按键分组
students.ToLookup(s => s.City)                  // 立即分组(非延迟)

集合运算

a.Union(b)        // 并集(去重)
a.Intersect(b)    // 交集
a.Except(b)       // 差集
a.Concat(b)       // 拼接
a.Distinct()      // 去重

量词(返回 bool)

students.Any(s => s.Score < 60)     // 是否有不及格的?
students.All(s => s.Score >= 60)    // 是否全部及格?
students.Contains(someStudent)      // 是否包含某个学生?

聚合(返回单个值)

students.Count()                    // 总人数
students.Count(s => s.Score >= 60)  // 及格人数
students.Sum(s => s.Score)          // 总分
students.Average(s => s.Score)      // 平均分
students.Max(s => s.Score)          // 最高分
students.Min(s => s.Score)          // 最低分

分区(分页)

students.Take(5)                    // 前5个
students.Skip(5)                    // 跳过前5个
students.Skip(10).Take(5)           // 第3页,每页5个

元素操作

students.First()                    // 第一个
students.First(s => s.Score > 90)   // 第一个 > 90 的
students.FirstOrDefault(s => s.Score > 100)  // 找不到返回 null
students.Last()                     // 最后一个
students.Single(s => s.Name == "张三")  // 唯一一个叫张三的
students.ElementAt(2)               // 索引为 2 的元素

转换

students.ToList()                   // 转 List
students.ToArray()                  // 转数组
students.ToDictionary(s => s.Name)  // 转字典(key 必须唯一)

5.3 方法语法的链式调用示例

// 需求:找出北京的学生,按分数降序,只要名字和分数,取前3
var result = students
    .Where(s => s.City == "北京")           // 1. 筛选
    .OrderByDescending(s => s.Score)         // 2. 排序
    .Select(s => new { s.Name, s.Score })    // 3. 投影
    .Take(3);                                // 4. 取前3

foreach (var item in result)
    Console.WriteLine($"{item.Name}: {item.Score}分");
// 输出: 周八: 95分  张三: 92分  王五: 76分

六、查询语法详解

6.1 查询语法的结构

查询语法以一个必选的 from ... in ... 开头,以必选的 selectgroup 结尾。

from 范围变量 in 数据源
[where 筛选条件]                    ← 可选
[orderby 排序列 [ascending|descending]]  ← 可选
[let 中间变量 = 表达式]              ← 可选
select 结果                         ← 或以 group 结尾

6.2 基本查询示例

// 最简查询
var all = from s in students select s;

// 带筛选
var passed = from s in students
             where s.Score >= 60
             select s;

// 带排序
var sorted = from s in students
             where s.Score >= 60
             orderby s.Score descending
             select s;

// 带 let
var withGrade = from s in students
                let grade = s.Score >= 90 ? "A" : s.Score >= 80 ? "B" : "C"
                where grade != "C"
                orderby s.Score descending
                select new { s.Name, Grade = grade, s.Score };

6.3 group by 详解

// 基本分组
var byCity = from s in students
             group s by s.City;

foreach (var group in byCity)
{
    Console.WriteLine($"--- {group.Key} ({group.Count()}人) ---");
    foreach (var s in group)
        Console.WriteLine($"  {s.Name}: {s.Score}分");
}

用 into 继续操作分组结果:

// group by ... into  —— 对分组结果进一步处理
var cityStats = from s in students
                group s by s.City into cityGroup      // cityGroup 是分组结果
                where cityGroup.Count() >= 2           // 只要 2 人及以上的城市
                orderby cityGroup.Average(s => s.Score) descending
                select new
                {
                    City = cityGroup.Key,
                    Count = cityGroup.Count(),
                    AvgScore = cityGroup.Average(s => s.Score)
                };

foreach (var stat in cityStats)
    Console.WriteLine($"{stat.City}: {stat.Count}人, 均分{stat.AvgScore:F1}");

输出:

上海: 2人, 均分86.5
北京: 3人, 均分87.7
广州: 2人, 均分51.5

6.4 join 详解

// 连表查询
var cityRatings = new List<CityRating>
{
    new CityRating { City = "北京", Rating = "一线", GDP = 40000 },
    new CityRating { City = "上海", Rating = "一线", GDP = 43000 },
    new CityRating { City = "广州", Rating = "一线", GDP = 28000 },
    new CityRating { City = "深圳", Rating = "一线", GDP = 30000 },
};

var joined = from s in students
             join c in cityRatings on s.City equals c.City
             where s.Score >= 60
             orderby s.Score descending
             select new
             {
                 s.Name,
                 s.City,
                 c.Rating,
                 c.GDP,
                 s.Score
             };

foreach (var item in joined)
    Console.WriteLine($"{item.Name} | {item.City} | {item.Rating} | GDP:{item.GDP}亿 | {item.Score}分");

输出:

周八 | 北京 | 一线 | GDP:40000亿 | 95分
张三 | 北京 | 一线 | GDP:40000亿 | 92分
孙七 | 上海 | 一线 | GDP:43000亿 | 88分
李四 | 上海 | 一线 | GDP:43000亿 | 85分
王五 | 北京 | 一线 | GDP:40000亿 | 76分
郑十 | 深圳 | 一线 | GDP:30000亿 | 72分

6.5 查询语法内部是如何工作的

查询语法是语法糖——编译器会把它翻译成方法语法:

// 你写的查询语法
var result = from s in students
             where s.Score >= 60
             orderby s.Score descending
             select s.Name;

// 编译器翻译成(等价于)
var result = students
    .Where(s => s.Score >= 60)
    .OrderByDescending(s => s.Score)
    .Select(s => s.Name);

七、两种语法如何混合使用?

你可以随时在查询语法后接方法调用——只要用括号把查询语法包起来:

// 查询语法 + 方法语法的混合
var result = (from s in students
              where s.Score >= 60
              orderby s.Score descending
              select s)
             .Take(3)      // ← 查询语法不支持 Take,用方法语法补上
             .ToList();

// 或者更直接
var result = (from s in students where s.Score >= 60 select s)
             .Count();

实用例子——查询语法写主体,方法语法做收尾:

// 查询语法做筛选排序,方法语法做聚合
var top3 = (from s in students
            where s.City == "北京"
            orderby s.Score descending
            select new { s.Name, s.Score })
           .Take(3)
           .ToList();

八、延迟执行——两种语法都一样!

重要:查询语法和方法语法都是延迟执行的。定义查询的时候不执行,遍历或调用 ToList() 等终止方法时才执行。

// ===== 查询语法也是延迟执行! =====
var query = from s in students
            where s.Score >= 80
            select s;

// 此时还没执行

// 现在才执行
foreach (var s in query)
    Console.WriteLine(s.Name);

// 修改数据源
students.Add(new Student { Name = "新人", Score = 100 });

// 再次遍历,结果包含新加的人!
Console.WriteLine($"现在有 {query.Count()} 人");  // 多了一个

立即执行 vs 延迟执行:

// 延迟:返回 IEnumerable<T>
var query1 = from s in students where s.Score >= 60 select s;  // 未执行
var query2 = students.Where(s => s.Score >= 60);               // 未执行

// 立即:返回具体值或具体集合
var list = students.Where(s => s.Score >= 60).ToList();  // 已执行
var count = students.Count(s => s.Score >= 60);          // 已执行

九、两种语法,什么时候用哪个?

用方法语法的场景(推荐初学者首选)

// ✅ 简单过滤、投影、排序
var result = students.Where(s => s.Score >= 60).OrderByDescending(s => s.Score);

// ✅ 聚合操作
int total = students.Sum(s => s.Score);
double avg = students.Average(s => s.Score);

// ✅ 分页
var page = students.Skip(10).Take(5);

// ✅ 不用 select 就能结束的链式调用
bool hasFailed = students.Any(s => s.Score < 60);

用查询语法的场景

// ✅ 复杂的 JOIN 连接(比方法语法直观很多)
var result = from s in students
             join c in cities on s.City equals c.City
             join t in teachers on s.Subject equals t.Subject
             where s.Score >= 60
             select new { s.Name, c.Province, t.TeacherName };

// ✅ 需要使用 let 创建中间变量(很优雅)
var result = from s in students
             let grade = s.Score >= 90 ? "A" : s.Score >= 80 ? "B" : "C"
             let isPassed = grade != "C"
             where isPassed
             select new { s.Name, grade, s.Score };

// ✅ group by ... into 多步分组操作
var result = from s in students
             group s by s.City into g
             where g.Count() >= 2
             orderby g.Average(s => s.Score) descending
             select new { City = g.Key, Avg = g.Average(s => s.Score) };

快速决策指南

你要做什么?
│
├─ 简单筛选/投影/排序  ──→  方法语法(.Where().Select().OrderBy())
│
├─ 聚合(求和/平均/计数)──→  方法语法(.Sum()/.Average()/.Count())
│
├─ 分页(Skip/Take)   ──→  方法语法(.Skip(n).Take(m))
│
├─ 复杂 JOIN 多表连接  ──→  查询语法(from ... join ... on ...)
│
├─ 需要 let 中间变量   ──→  查询语法(let x = ...)
│
├─ group by 后继续处理 ──→  查询语法(group ... into ...)
│
└─ 混合使用            ──→  查询语法写主体 + 方法语法收尾
                          (from ... select ...).Take(5).ToList()

十、完整实战示例——用两种语法实现相同报表

下面用同一个学生成绩报表,分别用两种语法实现:

10.1 用查询语法实现

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        List<Student> students = /* ... 前面的测试数据 ... */;

        Console.WriteLine("========== 学生成绩报表(查询语法) ==========\n");

        // 1. 按科目统计
        Console.WriteLine("【按科目统计】");
        var bySubject = from s in students
                        group s by s.Subject into g
                        orderby g.Average(s => s.Score) descending
                        select new
                        {
                            Subject = g.Key,
                            Count = g.Count(),
                            Avg = g.Average(s => s.Score),
                            Max = g.Max(s => s.Score),
                            Min = g.Min(s => s.Score)
                        };

        foreach (var subj in bySubject)
        {
            Console.WriteLine($"  {subj.Subject}: {subj.Count}人, "
                + $"均分{subj.Avg:F1}, 最高{subj.Max}, 最低{subj.Min}");
        }

        // 2. 按城市统计(只显示均分 ≥ 70 的城市)
        Console.WriteLine("\n【按城市统计(均分 ≥ 70)】");
        var byCity = from s in students
                     group s by s.City into g
                     let avg = g.Average(s => s.Score)
                     where avg >= 70
                     orderby avg descending
                     select new { City = g.Key, Count = g.Count(), Avg = avg };

        foreach (var city in byCity)
        {
            Console.WriteLine($"  {city.City}: {city.Count}人, 均分{city.Avg:F1}");
        }

        // 3. 光荣榜(取前3)
        Console.WriteLine("\n【光荣榜 Top 3】");
        var top3 = (from s in students
                    orderby s.Score descending
                    select s).Take(3);

        int rank = 1;
        foreach (var s in top3)
        {
            Console.WriteLine($"  {rank++}. {s}");
        }

        // 4. 需要补考的学生
        Console.WriteLine("\n【补考名单】");
        var needRetake = from s in students
                         where s.Score < 60
                         select new { s.Name, s.Subject, s.Score, Gap = 60 - s.Score };

        if (needRetake.Any())
        {
            foreach (var s in needRetake)
            {
                Console.WriteLine($"  {s.Name} - {s.Subject} - {s.Score}分 (差{s.Gap}分)");
            }
        }
        else
        {
            Console.WriteLine("  全员及格!");
        }
    }
}

10.2 用方法语法实现(同一报表)

static void Main()
{
    List<Student> students = /* ... 前面的测试数据 ... */;

    Console.WriteLine("========== 学生成绩报表(方法语法) ==========\n");

    // 1. 按科目统计
    Console.WriteLine("【按科目统计】");
    var bySubject = students
        .GroupBy(s => s.Subject)
        .Select(g => new
        {
            Subject = g.Key,
            Count = g.Count(),
            Avg = g.Average(s => s.Score),
            Max = g.Max(s => s.Score),
            Min = g.Min(s => s.Score)
        })
        .OrderByDescending(x => x.Avg);

    foreach (var subj in bySubject)
    {
        Console.WriteLine($"  {subj.Subject}: {subj.Count}人, "
            + $"均分{subj.Avg:F1}, 最高{subj.Max}, 最低{subj.Min}");
    }

    // 2. 按城市统计
    Console.WriteLine("\n【按城市统计(均分 ≥ 70)】");
    var byCity = students
        .GroupBy(s => s.City)
        .Select(g => new { City = g.Key, Count = g.Count(), Avg = g.Average(s => s.Score) })
        .Where(c => c.Avg >= 70)
        .OrderByDescending(c => c.Avg);

    foreach (var city in byCity)
    {
        Console.WriteLine($"  {city.City}: {city.Count}人, 均分{city.Avg:F1}");
    }

    // 3. 光荣榜
    Console.WriteLine("\n【光荣榜 Top 3】");
    var top3 = students.OrderByDescending(s => s.Score).Take(3);

    int rank = 1;
    foreach (var s in top3)
    {
        Console.WriteLine($"  {rank++}. {s}");
    }

    // 4. 补考名单
    Console.WriteLine("\n【补考名单】");
    var needRetake = students
        .Where(s => s.Score < 60)
        .Select(s => new { s.Name, s.Subject, s.Score, Gap = 60 - s.Score });

    if (needRetake.Any())
    {
        foreach (var s in needRetake)
        {
            Console.WriteLine($"  {s.Name} - {s.Subject} - {s.Score}分 (差{s.Gap}分)");
        }
    }
    else
    {
        Console.WriteLine("  全员及格!");
    }
}

两种写法的输出完全一致:

========== 学生成绩报表 ==========

【按科目统计】
  语文: 2人, 均分82.0, 最高88, 最低76
  数学: 4人, 均分76.8, 最高92, 最低58
  英语: 2人, 均分70.0, 最高95, 最低45

【按城市统计(均分 ≥ 70)】
  北京: 3人, 均分87.7
  上海: 2人, 均分86.5
  深圳: 1人, 均分72.0

【光荣榜 Top 3】
  1. 周八    | 20岁 | 北京  | 英语  | 95分
  2. 张三    | 18岁 | 北京  | 数学  | 92分
  3. 孙七    | 19岁 | 上海  | 语文  | 88分

【补考名单】
  赵六 - 数学 - 58分 (差2分)
  吴九 - 英语 - 45分 (差15分)

十一、常见易错点

坑1:查询语法最后必须 select 或 group

// ❌ 查询语法没有 select 或 group 结尾
// var x = from s in students where s.Score >= 60;  // 编译错误!

// ✅ 必须加 select 或 group
var x = from s in students where s.Score >= 60 select s;

坑2:查询语法不支持所有方法

// ❌ 查询语法不支持 Take、Skip、Count 等
// var x = from s in students where s.Score >= 60 take 5;  // 编译错误!

// ✅ 混合使用
var x = (from s in students where s.Score >= 60 select s).Take(5);

坑3:join 的 equals 不是 ==

// ❌ join ... on ... equals ... —— key 前后位置不是随便放的
// from s in students join c in cities on s.City == c.City  // 错误的语法!

// ✅ 用 equals 关键字
var x = from s in students
        join c in cities on s.City equals c.City
        select s;

坑4:orderby 多级排序用逗号

// ✅ 查询语法——多个排序条件用逗号
var x = from s in students
        orderby s.Age, s.Score descending
        select s;

// 对比方法语法——要调用多个方法
var y = students
    .OrderBy(s => s.Age)
    .ThenByDescending(s => s.Score);

十二、总结

两种语法的本质

查询语法 ──编译──→ 方法语法 ──执行──→ 结果

它们是同一个东西的两种写法!

核心对比

维度 查询语法 方法语法
风格 像 SQL 像链式调用
关键字 from, where, select, group, join, let .Where(), .Select(), .GroupBy() ...
可读性 复杂查询(JOIN、GROUP)更直观 简单操作更直接
功能覆盖 不完全(缺 Take/Skip/Count 等) 完全覆盖
IDE 支持 一般 更好(智能提示)
学习曲线 对 SQL 用户友好 对 C# 开发者直观
推荐 多表 JOIN、复杂分组 日常操作、聚合、分页

记忆口诀

查询语法像 SQL,from where select 排队
方法语法链式调,Where Select 搭积木

简单操作用方法,一行搞定不啰嗦
复杂连接用查询,join group 更清晰

两者本质是一家,编译器帮你翻
想收尾时混着用,括号包起再 Take

一句话总结:查询语法和方法语法是 LINQ 的两种写法,本质一样——编译器会把查询语法翻译成方法调用。简单操作用方法语法(.Where().Select().OrderBy()),复杂 JOIN 和分组用查询语法(from join group),两者可以随时混用。

0
博主关闭了当前页面的评论