.Net Core 学习:LINQ 详解

LINQ(Language Integrated Query,语言集成查询),C# 的声明式查询语言,是 C# 最具革命性的特性之一。它让你可以用类似 SQL 的语法来查询任何数据源,而不仅仅是数据库。

🎯 LINQ 是什么?

LINQ = SQL 风格的查询语法 + C# 的类型安全 + 编译时检查

cs 复制代码
// LINQ 查询示例:看起来像SQL,但完全是C#代码
var result = from book in books
             where book.Price > 50
             orderby book.Title
             select book.Title;

📊 LINQ 的三种形式

1. 查询语法 (Query Syntax) - 类似SQL

cs 复制代码
// 最直观,适合熟悉SQL的开发者
var cheapBooks = from book in books
                 where book.Price < 30
                 orderby book.Title descending
                 select new { book.Title, book.Author };

2. 方法语法 (Method Syntax) - Lambda表达式

cs 复制代码
// 更灵活,是实际编译的代码
var cheapBooks = books
    .Where(book => book.Price < 30)
    .OrderByDescending(book => book.Title)
    .Select(book => new { book.Title, book.Author });

3. 混合语法

cs 复制代码
var count = (from book in books
             where book.Price > 50
             select book).Count();

🔄 LINQ 提供程序(不同的数据源)

cs 复制代码
// 1. LINQ to Objects - 查询内存中的集合
List<Book> books = GetBooks();
var query = books.Where(b => b.Price > 50);

// 2. LINQ to Entities (EF Core) - 查询数据库
var dbQuery = context.Books.Where(b => b.Price > 50);

// 3. LINQ to XML - 查询XML文档
XDocument doc = XDocument.Load("books.xml");
var xmlQuery = from b in doc.Descendants("book")
               select b.Element("title").Value;

// 4. LINQ to JSON (Newtonsoft.Json) - 查询JSON
JArray jsonArray = JArray.Parse(jsonString);
var jsonQuery = jsonArray.Where(j => (int)j["price"] > 50);

📝 LINQ 核心操作详解

1. 筛选数据 (Filtering)

cs 复制代码
// Where - 基础筛选
var expensiveBooks = books.Where(b => b.Price > 100);

// 多个条件
var specialBooks = books.Where(b => 
    b.Price > 50 && 
    b.IsAvailable && 
    b.Author.Contains("King"));

// OfType - 筛选特定类型
object[] objects = { 1, "hello", 2.5, "world" };
var strings = objects.OfType<string>(); // 只返回 "hello", "world"

2. 排序数据 (Ordering)

cs 复制代码
// 单字段排序
var sortedByTitle = books.OrderBy(b => b.Title);
var sortedByPriceDesc = books.OrderByDescending(b => b.Price);

// 多字段排序
var multiSorted = books
    .OrderBy(b => b.Category)          // 先按类别
    .ThenBy(b => b.Price)              // 再按价格
    .ThenByDescending(b => b.Rating);  // 最后按评分降序

// 自定义比较器
var customSorted = books.OrderBy(b => b, new BookComparer());

3. 投影数据 (Projection)

cs 复制代码
// Select - 转换数据
var bookTitles = books.Select(b => b.Title);
var bookInfo = books.Select(b => new 
{
    Name = b.Title,
    Author = b.Author,
    PriceWithTax = b.Price * 1.1m
});

// SelectMany - 扁平化嵌套集合
var allAuthors = books.SelectMany(b => b.Authors);
// 假设每本书有多个作者,结果会是所有作者的扁平列表

4. 分组数据 (Grouping)

cs 复制代码
// GroupBy - 分组查询
var booksByAuthor = books.GroupBy(b => b.Author);

foreach (var group in booksByAuthor)
{
    Console.WriteLine($"作者: {group.Key}");
    Console.WriteLine($"作品数量: {group.Count()}");
    
    foreach (var book in group)
    {
        Console.WriteLine($"  - {book.Title}");
    }
}

// 复杂分组
var complexGroup = books.GroupBy(b => new 
{
    b.Author,
    Year = b.PublishDate.Year
});

// 分组后聚合
var statsByAuthor = books.GroupBy(b => b.Author)
    .Select(g => new
    {
        Author = g.Key,
        BookCount = g.Count(),
        AvgPrice = g.Average(b => b.Price),
        TotalSales = g.Sum(b => b.Sales)
    });

5. 连接数据 (Joining)

cs 复制代码
// 内连接
var bookDetails = from book in books
                  join author in authors 
                  on book.AuthorId equals author.Id
                  select new 
                  {
                      book.Title,
                      AuthorName = author.Name,
                      book.Price
                  };

// 左外连接
var leftJoin = from book in books
               join author in authors 
               on book.AuthorId equals author.Id into authorGroup
               from author in authorGroup.DefaultIfEmpty()
               select new 
               {
                   book.Title,
                   AuthorName = author?.Name ?? "未知作者"
               };

// 方法语法连接
var methodJoin = books.Join(authors,
    book => book.AuthorId,
    author => author.Id,
    (book, author) => new { book.Title, author.Name });

6. 聚合操作 (Aggregation)

cs 复制代码
// 基本聚合
var count = books.Count();
var totalPrice = books.Sum(b => b.Price);
var avgPrice = books.Average(b => b.Price);
var maxPrice = books.Max(b => b.Price);
var minPrice = books.Min(b => b.Price);

// 条件聚合
var availableCount = books.Count(b => b.IsAvailable);
var expensiveTotal = books.Where(b => b.Price > 100).Sum(b => b.Price);

// 分组聚合
var authorStats = books.GroupBy(b => b.Author)
    .Select(g => new
    {
        Author = g.Key,
        Books = g.Count(),
        TotalValue = g.Sum(b => b.Price * b.Stock)
    });

7. 分页和分区 (Paging)

cs 复制代码
// 分页查询
int pageSize = 10;
int pageNumber = 2;

var page = books
    .Skip((pageNumber - 1) * pageSize)  // 跳过前面的记录
    .Take(pageSize)                     // 取指定数量
    .ToList();

// 分区操作
var first5 = books.Take(5);             // 前5个
var last5 = books.TakeLast(5);          // 最后5个
var skip5 = books.Skip(5);              // 跳过前5个
var skipLast5 = books.SkipLast(5);      // 跳过最后5个

8. 集合操作 (Set Operations)

cs 复制代码
List<int> set1 = new() { 1, 2, 3, 4, 5 };
List<int> set2 = new() { 3, 4, 5, 6, 7 };

var distinct = set1.Distinct();                // 去重: 1,2,3,4,5
var union = set1.Union(set2);                  // 并集: 1,2,3,4,5,6,7
var intersect = set1.Intersect(set2);          // 交集: 3,4,5
var except = set1.Except(set2);                // 差集: 1,2
var concat = set1.Concat(set2);                // 连接: 1,2,3,4,5,3,4,5,6,7

9. 元素操作 (Element Operations)

cs 复制代码
// 获取单个元素
var first = books.First();                     // 第一个
var firstOrDefault = books.FirstOrDefault();   // 第一个或默认值
var last = books.Last();                       // 最后一个
var single = books.Single(b => b.Id == 1);     // 唯一匹配
var elementAt = books.ElementAt(2);            // 第三个元素

// 带条件的元素操作
var firstExpensive = books.First(b => b.Price > 100);
var lastAvailable = books.LastOrDefault(b => b.IsAvailable);

10. 量化操作 (Quantifiers)

cs 复制代码
bool anyExpensive = books.Any(b => b.Price > 1000);  // 是否有价格>1000的书
bool allAvailable = books.All(b => b.IsAvailable);   // 是否所有书都可借
bool contains = books.Any(b => b.Title == "C#入门"); // 是否包含特定书

🎨 LINQ 查询语法 vs 方法语法

等价示例对比

cs 复制代码
// 查询语法
var query1 = from book in books
             where book.Price > 50
             orderby book.Title
             select new { book.Title, book.Price };

// 方法语法
var query2 = books
    .Where(book => book.Price > 50)
    .OrderBy(book => book.Title)
    .Select(book => new { book.Title, book.Price });

只能使用方法语法的操作

cs 复制代码
// 1. 聚合操作
var count = books.Count(b => b.Price > 50);

// 2. 元素操作
var first = books.First();

// 3. 转换操作
var dictionary = books.ToDictionary(b => b.Id);
var lookup = books.ToLookup(b => b.Category);

// 4. 其他操作
var reversed = books.Reverse();
var zip = books.Zip(authors, (b, a) => new { b.Title, a.Name });

🔧 LINQ 在 EF Core 中的特殊用法

1. 延迟执行 (Deferred Execution)

cs 复制代码
// 查询定义(不执行)
var query = context.Books.Where(b => b.Price > 50);

// 查询仍未执行
query = query.OrderBy(b => b.Title);

// 触发执行的时机:
var list = query.ToList();        // 1. 转换为列表
var array = query.ToArray();      // 2. 转换为数组
var first = query.First();        // 3. 获取第一个元素
foreach (var item in query)       // 4. 遍历时
{
    // 在这里,查询才会真正执行
}

2. IQueryable 与 IEnumerable 的区别

cs 复制代码
// IQueryable - 在数据库执行查询
IQueryable<Book> dbQuery = context.Books;
var result1 = dbQuery.Where(b => b.Price > 50).ToList();
// SQL: SELECT * FROM Books WHERE Price > 50

// IEnumerable - 在内存中执行查询
IEnumerable<Book> memoryQuery = context.Books.AsEnumerable();
var result2 = memoryQuery.Where(b => b.Price > 50).ToList();
// SQL: SELECT * FROM Books
// 然后在内存中过滤(性能差!)

3. 客户端评估 vs 服务器评估

cs 复制代码
// ✅ 服务器端评估(在数据库执行)
var books1 = context.Books
    .Where(b => b.Price > 50)           // 转换为SQL WHERE条件
    .ToList();

// ⚠️ 客户端评估(在内存中执行,可能性能差)
var books2 = context.Books
    .ToList()                           // 先获取所有数据
    .Where(b => b.Price > 50);          // 在内存中过滤

// ❌ 无法转换为SQL的操作会引发异常
var books3 = context.Books
    .Where(b => b.Title.StartsWith("C#"))  // ✅ 可以转换
    .Where(b => SomeMethod(b.Price))        // ❌ 无法转换为SQL
    .ToList();

📚 实际应用示例

1. 电商产品查询

cs 复制代码
public IEnumerable<Product> SearchProducts(
    string keyword, 
    decimal? minPrice, 
    decimal? maxPrice,
    string category,
    int page = 1)
{
    var query = context.Products.AsQueryable();
    
    if (!string.IsNullOrEmpty(keyword))
    {
        query = query.Where(p => 
            p.Name.Contains(keyword) || 
            p.Description.Contains(keyword));
    }
    
    if (minPrice.HasValue)
        query = query.Where(p => p.Price >= minPrice.Value);
    
    if (maxPrice.HasValue)
        query = query.Where(p => p.Price <= maxPrice.Value);
    
    if (!string.IsNullOrEmpty(category))
        query = query.Where(p => p.Category == category);
    
    return query
        .OrderBy(p => p.Name)
        .Skip((page - 1) * PageSize)
        .Take(PageSize)
        .Select(p => new ProductDto
        {
            Id = p.Id,
            Name = p.Name,
            Price = p.Price,
            Category = p.Category,
            Stock = p.Stock
        });
}

2. 报表统计

cs 复制代码
public SalesReport GenerateMonthlyReport(int year, int month)
{
    var report = context.Orders
        .Where(o => o.OrderDate.Year == year && o.OrderDate.Month == month)
        .GroupBy(o => o.ProductId)
        .Select(g => new ProductSales
        {
            ProductId = g.Key,
            ProductName = g.First().Product.Name,
            TotalQuantity = g.Sum(o => o.Quantity),
            TotalRevenue = g.Sum(o => o.Quantity * o.UnitPrice),
            AveragePrice = g.Average(o => o.UnitPrice)
        })
        .OrderByDescending(p => p.TotalRevenue)
        .ToList();
    
    return new SalesReport
    {
        Month = month,
        Year = year,
        TotalRevenue = report.Sum(r => r.TotalRevenue),
        TopProducts = report.Take(10).ToList()
    };
}

💡 LINQ 最佳实践

1. 性能优化

cs 复制代码
// ❌ 不好:多次迭代
var expensiveBooks = books.Where(b => b.Price > 100);
int count = expensiveBooks.Count();          // 第一次迭代
var list = expensiveBooks.ToList();          // 第二次迭代

// ✅ 好:缓存结果
var expensiveBooksList = books
    .Where(b => b.Price > 100)
    .ToList();                               // 一次执行
int count = expensiveBooksList.Count;        // 使用缓存

2. 合理使用 Select

cs 复制代码
// ❌ 不好:选择过多数据
var allData = context.Books.ToList();       // 获取所有字段
var titles = allData.Select(b => b.Title);  // 在内存中投影

// ✅ 好:只选择需要的字段
var titles = context.Books
    .Select(b => b.Title)                    // 在数据库层面投影
    .ToList();

3. 避免在循环中使用 LINQ

cs 复制代码
// ❌ 不好:N+1查询问题
foreach (var author in authors)
{
    var books = context.Books
        .Where(b => b.AuthorId == author.Id)  // 每次循环都查询
        .ToList();
}

// ✅ 好:一次查询
var authorBooks = context.Books
    .GroupBy(b => b.AuthorId)                // 一次分组查询
    .ToDictionary(g => g.Key, g => g.ToList());

🔍 Java 开发者对照表

相关推荐
西岸行者3 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意3 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码3 天前
嵌入式学习路线
学习
毛小茛3 天前
计算机系统概论——校验码
学习
babe小鑫3 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms3 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下3 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。3 天前
2026.2.25监控学习
学习
im_AMBER3 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J3 天前
从“Hello World“ 开始 C++
c语言·c++·学习