Skip to main content

LINQ (Language Integrated Query)

什么是 LINQ?

核心概念

LINQ (Language Integrated Query) 是 .NET 中的语言集成查询,它提供了一种统一的语法来查询各种数据源(集合、数据库、XML等)。

LINQ 的优势

📝 统一语法 - 使用相同的查询语法处理不同数据源

🔍 类型安全 - 编译时检查,减少运行时错误

🎨 代码简洁 - 用更少的代码实现复杂查询

LINQ 的两种写法

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操作。

常用 LINQ 操作

1. 筛选 (Filtering)

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);

2. 投影 (Projection)

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 ? "及格" : "不及格"
});

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. 集合操作

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);

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 优化只读查询

相关资源