下面分别给出 LINQ to Objects (操作内存集合)和 LINQ to Entities(通过 EF Core 操作数据库)的 4 个典型案例。案例使用 C# 编写,并附带简要说明。
一、LINQ to Objects(4 个案例)
适用于 List<T>, T[], IEnumerable<T> 等内存中的集合。
案例 1:筛选(Where)与投影(Select)
场景:从学生列表中找出成绩大于 80 分的男生,只返回姓名和成绩。
csharp
public class Student
{
public string Name { get; set; }
public int Score { get; set; }
public string Gender { get; set; }
}
void Main()
{
List<Student> students = new List<Student>
{
new Student { Name = "张三", Score = 85, Gender = "男" },
new Student { Name = "李四", Score = 92, Gender = "男" },
new Student { Name = "王芳", Score = 78, Gender = "女" },
new Student { Name = "赵磊", Score = 88, Gender = "男" }
};
var result = students
.Where(s => s.Score > 80 && s.Gender == "男")
.Select(s => new { s.Name, s.Score });
foreach (var item in result)
Console.WriteLine($"{item.Name} - {item.Score}");
// 输出:张三 - 85, 李四 - 92, 赵磊 - 88
}
案例 2:排序(OrderBy / ThenBy)
场景:按成绩降序排列,成绩相同则按姓名升序。
csharp
var sorted = students
.OrderByDescending(s => s.Score)
.ThenBy(s => s.Name);
foreach (var s in sorted)
Console.WriteLine($"{s.Name}: {s.Score}");
案例 3:分组(GroupBy)
场景:按性别分组,统计每组人数和平均分。
csharp
var groups = students.GroupBy(s => s.Gender)
.Select(g => new
{
Gender = g.Key,
Count = g.Count(),
AvgScore = g.Average(x => x.Score)
});
foreach (var g in groups)
Console.WriteLine($"{g.Gender} 共 {g.Count} 人,平均分 {g.AvgScore:F1}");
案例 4:连接(Join)
场景:两个集合(学生表和班级表),通过班级 ID 连接,显示学生及其班级名称。
csharp
public class StudentWithClassId
{
public string Name { get; set; }
public int ClassId { get; set; }
}
public class Class
{
public int Id { get; set; }
public string Name { get; set; }
}
void Main()
{
List<StudentWithClassId> students = new List<StudentWithClassId>
{
new StudentWithClassId { Name = "张三", ClassId = 1 },
new StudentWithClassId { Name = "李四", ClassId = 2 },
};
List<Class> classes = new List<Class>
{
new Class { Id = 1, Name = "一班" },
new Class { Id = 2, Name = "二班" }
};
var joined = students.Join(classes,
student => student.ClassId,
cls => cls.Id,
(student, cls) => new { student.Name, ClassName = cls.Name });
foreach (var item in joined)
Console.WriteLine($"{item.Name} 属于 {item.ClassName}");
}
二、LINQ to Entities(4 个案例)
假设使用 Entity Framework Core ,上下文为 SchoolDbContext,包含 DbSet<Student> 和 DbSet<Class>。查询会被翻译成 SQL 执行。
案例 1:筛选与投影
场景:查询数据库中成绩大于 80 分的男生,只返回姓名和成绩。
csharp
using (var context = new SchoolDbContext())
{
var result = context.Students
.Where(s => s.Score > 80 && s.Gender == "男")
.Select(s => new { s.Name, s.Score })
.ToList(); // 此时执行 SQL
foreach (var item in result)
Console.WriteLine($"{item.Name} - {item.Score}");
}
案例 2:排序 + 取前 N 条
场景:按成绩降序取前 3 名的学生。
csharp
using (var context = new SchoolDbContext())
{
var top3 = context.Students
.OrderByDescending(s => s.Score)
.Take(3)
.ToList();
foreach (var s in top3)
Console.WriteLine($"{s.Name}: {s.Score}");
}
案例 3:分组聚合(GroupBy)
场景:按性别分组,统计每组人数和最高分。
csharp
using (var context = new SchoolDbContext())
{
var groupStats = context.Students
.GroupBy(s => s.Gender)
.Select(g => new
{
Gender = g.Key,
Count = g.Count(),
MaxScore = g.Max(x => x.Score)
})
.ToList();
foreach (var g in groupStats)
Console.WriteLine($"{g.Gender} 共 {g.Count} 人,最高分 {g.MaxScore}");
}
注意:EF Core 会将上述 GroupBy 转换为 SQL 的
GROUP BY,不会将数据全部拉到内存。
案例 4:跨表连接(Join / Include)
场景:查询学生及其所属班级名称。两种写法:
方法 A:使用 Join(类似 SQL)
csharp
using (var context = new SchoolDbContext())
{
var query = context.Students
.Join(context.Classes,
s => s.ClassId,
c => c.Id,
(s, c) => new { s.Name, ClassName = c.Name })
.ToList();
}
方法 B:使用导航属性(推荐,如果模型已配置关系)
csharp
// 假设 Student 实体包含 public Class Class { get; set; }
var result = context.Students
.Include(s => s.Class) // 避免 N+1 问题
.Select(s => new { s.Name, ClassName = s.Class.Name })
.ToList();
关键区别总结
| 特性 | LINQ to Objects | LINQ to Entities |
|---|---|---|
| 数据源 | 内存集合(List, Array等) | 数据库(通过 EF Core) |
| 执行方式 | 立即执行,委托直接调用 | 延迟执行,生成 SQL 语句 |
| 支持的方法 | 几乎所有 LINQ 运算符 | 受 SQL 限制(例如不能随意调用自定义 C# 方法) |
| 典型终止操作 | .ToList(), .First(), .Count() 等 |
同样需要 .ToList() 等来触发 SQL 执行 |
掌握这两者,基本就能覆盖绝大多数 .NET 日常开发中的数据查询需求。
继续补充 LINQ to Objects 和 LINQ to Entities 各 4 个新案例,避免与上一组重复。
一、LINQ to Objects(追加 4 例)
案例 5:聚合函数(Min, Max, Sum, Average)
场景:对一组订单金额进行计算,获取总额、最高金额、最低金额、平均金额。
csharp
List<double> orderAmounts = new List<double> { 99.5, 150.0, 320.8, 45.2, 270.3 };
double total = orderAmounts.Sum();
double max = orderAmounts.Max();
double min = orderAmounts.Min();
double avg = orderAmounts.Average();
Console.WriteLine($"总额:{total},最高:{max},最低:{min},平均:{avg:F2}");
案例 6:集合操作(Distinct, Union, Intersect, Except)
场景:两个兴趣集合,求并集、交集、差集、去重。
csharp
List<string> groupA = new List<string> { "篮球", "足球", "游泳" };
List<string> groupB = new List<string> { "足球", "羽毛球", "游泳" };
var union = groupA.Union(groupB); // 并集:篮球,足球,游泳,羽毛球
var intersect = groupA.Intersect(groupB); // 交集:足球,游泳
var except = groupA.Except(groupB); // 差集:篮球
var distinct = groupA.Distinct(); // 本身去重(本例无变化)
Console.WriteLine($"并集:{string.Join(",", union)}");
Console.WriteLine($"交集:{string.Join(",", intersect)}");
Console.WriteLine($"差集:{string.Join(",", except)}");
案例 7:元素操作(First, Last, Single 及 OrDefault 版本)
场景:从集合中获取特定位置的元素,安全处理空值。
csharp
List<int> numbers = new List<int> { 10, 20, 30, 40, 50 };
List<int> emptyList = new List<int>();
int first = numbers.First(); // 10
int last = numbers.Last(); // 50
int firstEven = numbers.First(n => n % 20 == 0); // 20
// 安全版本(不存在时返回默认值,不抛异常)
int firstOrDefault = emptyList.FirstOrDefault(); // 0
int singleOrDefault = numbers.SingleOrDefault(n => n > 100); // 0
// Single:要求集合中恰好有一个匹配元素
int onlyOne = new List<int> { 100 }.Single(); // 100
案例 8:量词操作(Any, All, Contains)
场景:判断集合中是否存在满足条件的元素,或是否全部满足。
csharp
List<int> ages = new List<int> { 18, 20, 25, 17, 30 };
bool hasMinor = ages.Any(age => age < 18); // true(有17岁)
bool allAdult = ages.All(age => age >= 18); // false(存在17)
bool contains25 = ages.Contains(25); // true
if (hasMinor)
Console.WriteLine("存在未成年人");
二、LINQ to Entities(追加 4 例)
仍假设使用 EF Core,上下文为 AppDbContext,实体如 Product(含 Name, Price, Category, CreatedAt)、Order 等。
案例 5:模糊查询与日期范围筛选
场景:查询名称包含"手机"的产品,且生产日期在去年全年范围内。
csharp
using (var context = new AppDbContext())
{
var startDate = new DateTime(2025, 1, 1);
var endDate = new DateTime(2025, 12, 31);
var products = context.Products
.Where(p => EF.Functions.Like(p.Name, "%手机%")
&& p.CreatedAt >= startDate
&& p.CreatedAt <= endDate)
.ToList();
}
EF.Functions.Like会被翻译成 SQL 的LIKE,比Contains更灵活。
案例 6:分页(Skip + Take)
场景:每页显示 10 条商品,按价格升序,获取第 3 页的数据。
csharp
int pageNumber = 3;
int pageSize = 10;
using (var context = new AppDbContext())
{
var page = context.Products
.OrderBy(p => p.Price)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToList();
}
案例 7:子查询与 Exists(用 Any 实现)
场景:查询所有下过订单的客户(即客户在 Orders 表中存在关联记录)。
csharp
using (var context = new AppDbContext())
{
// 方法1:直接使用 Any 子查询(会被翻译为 EXISTS)
var customersWithOrders = context.Customers
.Where(c => context.Orders.Any(o => o.CustomerId == c.Id))
.ToList();
// 方法2:Join 后 Distinct(效率通常不如 Any)
var customersWithOrders2 = context.Customers
.Join(context.Orders, c => c.Id, o => o.CustomerId, (c, o) => c)
.Distinct()
.ToList();
}
案例 8:多级分组(GroupBy 多个字段)
场景:按商品分类和上架年份分组,统计每组的商品数量和平均价格。
csharp
using (var context = new AppDbContext())
{
var query = context.Products
.GroupBy(p => new { p.Category, Year = p.CreatedAt.Year })
.Select(g => new
{
g.Key.Category,
g.Key.Year,
Count = g.Count(),
AvgPrice = g.Average(p => p.Price)
})
.OrderBy(r => r.Category)
.ThenBy(r => r.Year)
.ToList();
foreach (var item in query)
Console.WriteLine($"{item.Category} - {item.Year}:{item.Count} 件,均价 {item.AvgPrice:F2}");
}
EF Core 会将
CreatedAt.Year翻译为 SQL 的YEAR(CreatedAt),支持常见日期函数。
再次总结两组案例的核心区别
| 操作类型 | LINQ to Objects | LINQ to Entities |
|---|---|---|
| 自定义方法调用 | ✅ 完全支持(如自定义比较器) | ❌ 仅支持 EF Core 映射的函数 |
| 延迟执行 | 内存中遍历时执行 | 生成 SQL,ToList() 时执行 |
| 异常处理 | 直接抛出 .NET 异常 | 可能抛出数据库相关异常 |
| 性能考虑 | 数据量过大时 O(n) 扫描 | 依赖数据库索引和查询优化 |
如果需要更多案例(如 SelectMany、Zip、ToDictionary 等),我可以继续提供。