LINQ (Language Integrated Query)
什么是 LINQ?
核心概念
LINQ (Language Integrated Query) 是 .NET 中的语言集成查询,它提供了一种统一的语法来查询各种数据源(集合、数据库、XML等)。
LINQ 的优势
📝 统一语法 - 使用相同的查询语法处理不同数据源
🔍 类型安全 - 编译时检查,减少运行时错误
🎨 代码简洁 - 用更少的代码实现复杂查询
LINQ 的两种写法
- 方法语法(Lambda)
- 查询语法(SQL风格)
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evenNumbers = numbers
.Where(n => n % 2 == 0)
.Select(n => n * n)
.OrderByDescending(n => n);
// 结果: [100, 64, 36, 16, 4]
推荐
方法语法更简洁,是最常用的写法,支持所有LINQ操作。
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evenNumbers = from n in numbers
where n % 2 == 0
orderby n * n descending
select n * n;
// 结果: [100, 64, 36, 16, 4]
说明
查询语法类似SQL,适合复杂的多表查询,但某些操作(如Skip
、Take
)仍需方法语法。
常用 LINQ 操作
1. 筛选 (Filtering)
- Where
- OfType
var students = new List<Student>
{
new Student { Name = "张三", Age = 20, Score = 85 },
new Student { Name = "李四", Age = 22, Score = 92 },
new Student { Name = "王五", Age = 19, Score = 78 }
};
// 筛选成绩大于80的学生
var goodStudents = students.Where(s => s.Score > 80);
// 多个条件
var result = students.Where(s => s.Score > 80 && s.Age < 21);
var mixed = new object[] { 1, "hello", 2, "world", 3.14, 42 };
// 只获取整数类型
var integers = mixed.OfType<int>();
// 结果: [1, 2, 42]
// 只获取字符串类型
var strings = mixed.OfType<string>();
// 结果: ["hello", "world"]
2. 投影 (Projection)
- Select
- SelectMany
var students = GetStudents();
// 投影到新属性
var names = students.Select(s => s.Name);
// 投影到匿名类型
var summary = students.Select(s => new
{
s.Name,
s.Score,
Grade = s.Score >= 90 ? "优秀" :
s.Score >= 80 ? "良好" :
s.Score >= 60 ? "及格" : "不及格"
});
var classes = new List<Class>
{
new Class { Name = "一班", Students = new[] { "张三", "李四" } },
new Class { Name = "二班", Students = new[] { "王五", "赵六" } }
};
// 展平嵌套集合
var allStudents = classes.SelectMany(c => c.Students);
// 结果: ["张三", "李四", "王五", "赵六"]
// 带索引的展平
var studentsWithClass = classes.SelectMany(
c => c.Students,
(c, student) => $"{c.Name} - {student}"
);
// 结果: ["一班 - 张三", "一班 - 李四", "二班 - 王五", "二班 - 赵六"]
3. 排序 (Ordering)
var students = GetStudents();
// 升序排序
var ordered = students.OrderBy(s => s.Score);
// 降序排序
var descOrdered = students.OrderByDescending(s => s.Score);
// 多级排序
var multiOrdered = students
.OrderByDescending(s => s.Score) // 先按分数降序
.ThenBy(s => s.Age); // 再按年龄升序
4. 分组 (Grouping)
var students = GetStudents();
// 按成绩分组
var grouped = students.GroupBy(s => s.Score >= 60 ? "及格" : "不及格");
foreach (var group in grouped)
{
Console.WriteLine($"{group.Key}:");
foreach (var student in group)
{
Console.WriteLine($" - {student.Name}: {student.Score}");
}
}
// 分组后投影
var summary = students
.GroupBy(s => s.Age)
.Select(g => new
{
Age = g.Key,
Count = g.Count(),
AvgScore = g.Average(s => s.Score)
});
5. 聚合 (Aggregation)
操作 | 说明 | 示例 |
---|---|---|
Count() | 计数 | students.Count() |
Sum() | 求和 | students.Sum(s => s.Score) |
Average() | 平均值 | students.Average(s => s.Score) |
Min() | 最小值 | students.Min(s => s.Score) |
Max() | 最大值 | students.Max(s => s.Score) |
Aggregate() | 自定义聚合 | numbers.Aggregate((a, b) => a * b) |
var students = GetStudents();
// 基础聚合
var count = students.Count();
var avgScore = students.Average(s => s.Score);
var topScore = students.Max(s => s.Score);
// 自定义聚合
var totalScore = students.Aggregate(0, (total, s) => total + s.Score);
// 复杂聚合
var stats = new
{
Total = students.Count(),
Passed = students.Count(s => s.Score >= 60),
AvgScore = students.Average(s => s.Score),
TopScore = students.Max(s => s.Score)
};
6. 连接 (Joining)
var students = new List<Student>
{
new Student { Id = 1, Name = "张三", ClassId = 1 },
new Student { Id = 2, Name = "李四", ClassId = 2 },
new Student { Id = 3, Name = "王五", ClassId = 1 }
};
var classes = new List<Class>
{
new Class { Id = 1, Name = "一班" },
new Class { Id = 2, Name = "二班" }
};
// Inner Join
var result = students.Join(
classes,
student => student.ClassId,
cls => cls.Id,
(student, cls) => new
{
student.Name,
ClassName = cls.Name
}
);
// Left Join (使用 GroupJoin)
var leftJoin = students.GroupJoin(
classes,
student => student.ClassId,
cls => cls.Id,
(student, classGroup) => new
{
student.Name,
ClassName = classGroup.FirstOrDefault()?.Name ?? "无班级"
}
);
7. 集合操作
- Distinct
- Union / Intersect / Except
var numbers = new[] { 1, 2, 2, 3, 3, 3, 4, 5, 5 };
// 去重
var distinct = numbers.Distinct();
// 结果: [1, 2, 3, 4, 5]
// 复杂对象去重
var uniqueStudents = students.DistinctBy(s => s.Name);
var list1 = new[] { 1, 2, 3, 4, 5 };
var list2 = new[] { 4, 5, 6, 7, 8 };
// 并集
var union = list1.Union(list2);
// 结果: [1, 2, 3, 4, 5, 6, 7, 8]
// 交集
var intersect = list1.Intersect(list2);
// 结果: [4, 5]
// 差集
var except = list1.Except(list2);
// 结果: [1, 2, 3]
8. 分页与限制
var students = GetStudents();
// 跳过前10条
var skipped = students.Skip(10);
// 取前10条
var taken = students.Take(10);
// 分页:第3页,每页20条
int pageIndex = 3;
int pageSize = 20;
var page = students
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize);
// TakeLast, SkipLast (.NET 6+)
var lastThree = students.TakeLast(3);
var skipLastTwo = students.SkipLast(2);
9. 量词操作
var students = GetStudents();
// 是否所有学生都及格
var allPassed = students.All(s => s.Score >= 60);
// 是否存在满分学生
var hasFullScore = students.Any(s => s.Score == 100);
// 是否包含特定学生
var hasZhangSan = students.Any(s => s.Name == "张三");
// 是否为空
var isEmpty = !students.Any();
10. 元素操作
var numbers = new[] { 1, 2, 3, 4, 5 };
// First: 获取第一个元素(没有会抛异常)
var first = numbers.First();
var firstEven = numbers.First(n => n % 2 == 0); // 2
// FirstOrDefault: 获取第一个元素或默认值
var firstOrDefault = numbers.FirstOrDefault(n => n > 10); // 0
// Last / LastOrDefault
var last = numbers.Last(); // 5
// Single: 确保只有一个元素(否则抛异常)
var single = numbers.Where(n => n == 3).Single(); // 3
// ElementAt: 按索引获取
var third = numbers.ElementAt(2); // 3
LINQ 查询执行机制
延迟执行 (Deferred Execution)
大多数 LINQ 操作是延迟执行的,查询在枚举时才真正执行。
var numbers = new[] { 1, 2, 3, 4, 5 };
// 此时查询还没有执行
var query = numbers.Where(n => n > 2);
// 在迭代时才执行
foreach (var n in query) // 👈 这里才真正执行查询
{
Console.WriteLine(n);
}
立即执行操作
以下操作会立即执行查询:
var numbers = new[] { 1, 2, 3, 4, 5 };
// 这些方法会立即执行
var count = numbers.Where(n => n > 2).Count(); // 聚合操作
var list = numbers.Where(n => n > 2).ToList(); // 转换操作
var array = numbers.Where(n => n > 2).ToArray(); // 转换操作
var dict = students.ToDictionary(s => s.Id); // 转换操作
var first = numbers.First(n => n > 2); // 元素操作
实战案例
案例1:复杂数据统计
public class OrderStatistics
{
public async Task<OrderSummary> GetOrderSummaryAsync(DateTime startDate, DateTime endDate)
{
var orders = await _orderRepository.GetOrdersByDateRangeAsync(startDate, endDate);
return new OrderSummary
{
// 总订单数
TotalOrders = orders.Count(),
// 总金额
TotalAmount = orders.Sum(o => o.Amount),
// 平均订单金额
AverageAmount = orders.Average(o => o.Amount),
// 按状态分组统计
OrdersByStatus = orders
.GroupBy(o => o.Status)
.Select(g => new
{
Status = g.Key,
Count = g.Count(),
Amount = g.Sum(o => o.Amount)
})
.ToList(),
// Top 10 客户
TopCustomers = orders
.GroupBy(o => o.CustomerId)
.Select(g => new
{
CustomerId = g.Key,
OrderCount = g.Count(),
TotalAmount = g.Sum(o => o.Amount)
})
.OrderByDescending(x => x.TotalAmount)
.Take(10)
.ToList()
};
}
}
案例2:动态查询
public IQueryable<Product> SearchProducts(ProductSearchCriteria criteria)
{
IQueryable<Product> query = _dbContext.Products;
// 动态添加过滤条件
if (!string.IsNullOrEmpty(criteria.Name))
{
query = query.Where(p => p.Name.Contains(criteria.Name));
}
if (criteria.MinPrice.HasValue)
{
query = query.Where(p => p.Price >= criteria.MinPrice);
}
if (criteria.MaxPrice.HasValue)
{
query = query.Where(p => p.Price <= criteria.MaxPrice);
}
if (criteria.CategoryIds?.Any() == true)
{
query = query.Where(p => criteria.CategoryIds.Contains(p.CategoryId));
}
// 动态排序
query = criteria.SortBy switch
{
"price_asc" => query.OrderBy(p => p.Price),
"price_desc" => query.OrderByDescending(p => p.Price),
"name" => query.OrderBy(p => p.Name),
_ => query.OrderBy(p => p.Id)
};
// 分页
return query
.Skip((criteria.Page - 1) * criteria.PageSize)
.Take(criteria.PageSize);
}
案例3:LINQ to Entities (EF Core)
public class ProductService
{
private readonly AppDbContext _context;
// 复杂查询
public async Task<List<ProductDto>> GetProductsWithDetailsAsync()
{
return await _context.Products
.Include(p => p.Category) // 关联查询
.Include(p => p.Reviews) // 关联查询
.Where(p => p.IsActive) // 筛选
.Select(p => new ProductDto // 投影
{
Id = p.Id,
Name = p.Name,
Price = p.Price,
CategoryName = p.Category.Name,
AverageRating = p.Reviews.Average(r => r.Rating),
ReviewCount = p.Reviews.Count
})
.OrderByDescending(p => p.AverageRating)
.ToListAsync();
}
}
性能优化技巧
最佳实践
1. 使用 IQueryable 延迟执行
// ✅ 好:在数据库层面过滤
public IQueryable<Product> GetExpensiveProducts(decimal minPrice)
{
return _context.Products.Where(p => p.Price > minPrice);
// 查询在调用 ToList() 时才执行
}
// ❌ 差:加载所有数据到内存再过滤
public List<Product> GetExpensiveProductsBad(decimal minPrice)
{
return _context.Products
.ToList() // 👈 立即加载所有数据
.Where(p => p.Price > minPrice) // 在内存中过滤
.ToList();
}
2. 避免 N+1 查询
// ❌ N+1 问题
var orders = _context.Orders.ToList();
foreach (var order in orders)
{
// 每个订单都会查询一次数据库
var customer = _context.Customers.Find(order.CustomerId);
}
// ✅ 使用 Include 预加载
var orders = _context.Orders
.Include(o => o.Customer)
.ToList();
3. 只查询需要的字段
// ❌ 查询所有字段
var products = _context.Products.ToList();
// ✅ 只查询需要的字段
var products = _context.Products
.Select(p => new { p.Id, p.Name, p.Price })
.ToList();
4. 使用 AsNoTracking
// 只读查询使用 AsNoTracking 提高性能
var products = await _context.Products
.AsNoTracking() // 👈 不跟踪实体变化
.ToListAsync();
常见陷阱
❌ 陷阱1:多次枚举
// ❌ 问题:多次执行查询
var query = GetExpensiveQuery();
var count = query.Count(); // 执行一次
var first = query.FirstOrDefault(); // 再执行一次
// ✅ 解决:缓存结果
var results = GetExpensiveQuery().ToList(); // 执行一次
var count = results.Count;
var first = results.FirstOrDefault();
❌ 陷阱2:在 LINQ 中调用复杂方法
// ❌ EF Core 无法将此方法转换为 SQL
var products = _context.Products
.Where(p => IsExpensive(p.Price)) // 👈 会抛异常
.ToList();
// ✅ 方案1:使用简单表达式
var products = _context.Products
.Where(p => p.Price > 1000)
.ToList();
// ✅ 方案2:先查询再在内存中过滤
var products = _context.Products
.ToList() // 先查询
.Where(p => IsExpensive(p.Price)) // 再过滤
.ToList();
总结
关键要点
- LINQ 提供统一、类型安全的查询语法
- 优先使用方法语法(Lambda),更简洁灵活
- 理解延迟执行和立即执行的区别
- 在 EF Core 中使用
IQueryable
优化性能 - 避免 N+1 查询和不必要的数据加载
- 使用
AsNoTracking
优化只读查询