.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 开发者对照表

相关推荐
van久1 小时前
.NET Core 学习第三天:Razor Pages 联表查询
学习·.netcore
逆小舟1 小时前
【STM32】第四周学习问题汇总
学习
qq_401780822 小时前
2.信号 完整性(信号上升时间与带宽)
学习
车载测试工程师2 小时前
CAPL学习-ETH功能函数-方法类3
学习·tcp/ip·以太网·capl·canoe
im_AMBER2 小时前
Leetcode 69 正整数和负整数的最大计数
数据结构·笔记·学习·算法·leetcode
richxu202510012 小时前
嵌入式学习之路>单片机核心原理篇>(5)串口通信核心原理
单片机·嵌入式硬件·学习
sponge'2 小时前
opencv学习笔记12:GAN网络
笔记·opencv·学习
会飞的小蛮猪2 小时前
Rockylinux急速安装K8s学习环境
学习·容器·kubernetes
代码游侠2 小时前
数据结构--队列
数据结构·笔记·学习·算法·链表