C# LINQ详解

C# LINQ详解

LINQ(Language Integrated Query)是C#中一项革命性的技术,它将查询功能直接集成到C#语言中,使开发者能够以声明式的方式查询各种数据源。本文将全面深入地介绍C#中LINQ的各种特性、用法和最佳实践。

一、LINQ概述

1.1 LINQ是什么?

LINQ(Language Integrated Query)是.NET Framework 3.5引入的一项技术,它允许开发者使用类似SQL的语法来查询各种数据源,包括:

  • 内存中的集合(如数组、列表)
  • 数据库(SQL Server、Oracle等)
  • XML文档
  • Web服务
  • 其他实现了IEnumerable或IQueryable接口的数据源

1.2 LINQ的核心组件

LINQ主要由以下几个部分组成:

  1. ​LINQ to Objects​​:查询内存中的集合
  2. ​LINQ to SQL​​:查询SQL Server数据库(已逐渐被Entity Framework取代)
  3. ​LINQ to Entities​​:通过Entity Framework查询各种数据库
  4. ​LINQ to XML​​:查询和操作XML文档
  5. ​LINQ to DataSet​​:查询DataSet对象
  6. ​并行LINQ(PLINQ)​​:提供并行查询功能

二、LINQ基础语法

2.1 查询表达式语法(Query Syntax)

查询表达式语法类似于SQL,使用关键字如fromwhereselect等:

var query = from p in products
            where p.Price > 100
            orderby p.Name
            select p;

2.2 方法语法(Method Syntax)

方法语法使用扩展方法,如Where()Select()等:

var query = products.Where(p => p.Price > 100)
                   .OrderBy(p => p.Name)
                   .Select(p => p);

2.3 混合使用

查询表达式和方法语法可以混合使用:

var query = from p in products
            where p.Price > 100
            orderby p.Name
            select p.Name.ToUpper(); // 这里使用了方法语法

三、LINQ操作符详解

3.1 筛选操作符

操作符描述示例
Where筛选满足条件的元素products.Where(p => p.Price > 100)
OfType筛选指定类型的元素collection.OfType<string>()

3.2 投影操作符

操作符描述示例
Select投影新序列products.Select(p => p.Name)
SelectMany将嵌套集合展平customers.SelectMany(c => c.Orders)

3.3 分区操作符

操作符描述示例
Take返回前N个元素products.Take(5)
TakeWhile返回满足条件的元素,直到条件不满足products.TakeWhile(p => p.Price < 100)
Skip跳过前N个元素products.Skip(5)
SkipWhile跳过满足条件的元素,直到条件不满足products.SkipWhile(p => p.Price < 100)

3.4 排序操作符

操作符描述示例
OrderBy升序排序products.OrderBy(p => p.Price)
OrderByDescending降序排序products.OrderByDescending(p => p.Price)
ThenBy次级排序(升序)products.OrderBy(p => p.Category).ThenBy(p => p.Price)
ThenByDescending次级排序(降序)products.OrderBy(p => p.Category).ThenByDescending(p => p.Price)

3.5 分组操作符

操作符描述示例
GroupBy按键分组products.GroupBy(p => p.Category)
ToLookup创建查找表products.ToLookup(p => p.Category)

3.6 聚合操作符

操作符描述示例
Count计数products.Count()
LongCount长整型计数products.LongCount()
Sum求和products.Sum(p => p.Price)
Min最小值products.Min(p => p.Price)
Max最大值products.Max(p => p.Price)
Average平均值products.Average(p => p.Price)
Aggregate自定义聚合products.Aggregate((acc, p) => acc + p.Price)

3.7 集合操作符

操作符描述示例
Distinct去重products.Select(p => p.Category).Distinct()
Union并集list1.Union(list2)
Intersect交集list1.Intersect(list2)
Except差集list1.Except(list2)
Concat连接list1.Concat(list2)

3.8 生成操作符

操作符描述示例
Range生成数字序列Enumerable.Range(1, 10)
Repeat重复元素Enumerable.Repeat("A", 5)
Empty空序列Enumerable.Empty<int>()
DefaultIfEmpty默认空值products.DefaultIfEmpty(new Product())

3.9 元素操作符

操作符描述示例
First第一个元素products.First()
FirstOrDefault第一个元素或默认值products.FirstOrDefault()
Last最后一个元素products.Last()
LastOrDefault最后一个元素或默认值products.LastOrDefault()
Single唯一元素products.Single(p => p.Id == 1)
SingleOrDefault唯一元素或默认值products.SingleOrDefault(p => p.Id == 1)
ElementAt指定索引元素products.ElementAt(2)
ElementAtOrDefault指定索引元素或默认值products.ElementAtOrDefault(2)

3.10 转换操作符

操作符描述示例
ToList转换为Listproducts.ToList()
ToArray转换为数组products.ToArray()
ToDictionary转换为字典products.ToDictionary(p => p.Id)
ToLookup转换为查找表products.ToLookup(p => p.Category)

四、LINQ查询执行机制

4.1 延迟执行

大多数LINQ操作都是延迟执行的,只有在实际枚举结果时才会执行查询:

var query = products.Where(p => p.Price > 100); // 查询未执行

foreach (var p in query) // 查询在此处执行
{
    Console.WriteLine(p.Name);
}

4.2 立即执行

某些操作会立即执行并缓存结果:

var list = products.Where(p => p.Price > 100).ToList(); // 立即执行并缓存

五、LINQ to Objects详解

LINQ to Objects用于查询内存中的集合,是最常用的LINQ形式。

5.1 基本查询

List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

// 查询偶数
var evenNumbers = numbers.Where(n => n % 2 == 0);

// 查询大于5的数并按降序排序
var largeNumbers = numbers.Where(n => n > 5)
                         .OrderByDescending(n => n);

// 查询前3个大于5的数
var top3LargeNumbers = numbers.Where(n => n > 5)
                             .OrderByDescending(n => n)
                             .Take(3);

5.2 复杂查询

class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}

List<Product> products = new List<Product>
{
    new Product { Id = 1, Name = "Laptop", Price = 1200, Category = "Electronics" },
    new Product { Id = 2, Name = "Phone", Price = 800, Category = "Electronics" },
    new Product { Id = 3, Name = "Desk", Price = 200, Category = "Furniture" },
    new Product { Id = 4, Name = "Chair", Price = 100, Category = "Furniture" },
    new Product { Id = 5, Name = "Tablet", Price = 500, Category = "Electronics" }
};

// 查询每个类别中最贵的产品
var mostExpensiveInEachCategory = products
    .GroupBy(p => p.Category)
    .Select(g => g.OrderByDescending(p => p.Price).First());

// 查询价格在200到800之间的产品,并按价格排序
var midRangeProducts = products
    .Where(p => p.Price >= 200 && p.Price <= 800)
    .OrderBy(p => p.Price);

// 查询每个类别的产品数量
var categoryCounts = products
    .GroupBy(p => p.Category)
    .Select(g => new { Category = g.Key, Count = g.Count() });

六、LINQ to XML详解

LINQ to XML提供了强大的XML处理能力。

6.1 创建XML文档

XDocument doc = new XDocument(
    new XDeclaration("1.0", "utf-8", "yes"),
    new XElement("Books",
        new XElement("Book",
            new XAttribute("Id", 1),
            new XElement("Title", "C# in Depth"),
            new XElement("Author", "Jon Skeet")
        ),
        new XElement("Book",
            new XAttribute("Id", 2),
            new XElement("Title", "Clean Code"),
            new XElement("Author", "Robert C. Martin")
        )
    )
);

doc.Save("books.xml");

6.2 查询XML文档

XDocument doc = XDocument.Load("books.xml");

// 查询所有书籍
var books = doc.Descendants("Book");

// 查询特定作者的书籍
var martinBooks = doc.Descendants("Book")
                     .Where(b => (string)b.Element("Author") == "Robert C. Martin");

// 查询书籍ID为1的标题
var bookTitle = doc.Descendants("Book")
                  .Where(b => (int)b.Attribute("Id") == 1)
                  .Select(b => (string)b.Element("Title"))
                  .FirstOrDefault();

// 创建新书籍并添加到文档
var newBook = new XElement("Book",
    new XAttribute("Id", 3),
    new XElement("Title", "Effective C#"),
    new XElement("Author", "Bill Wagner")
);

doc.Root.Add(newBook);
doc.Save("books.xml");

七、LINQ to SQL/Entities详解

虽然LINQ to SQL已逐渐被Entity Framework取代,但了解其基本用法仍然有用。

7.1 LINQ to SQL基本用法

// 定义实体类
[Table(Name = "Customers")]
public class Customer
{
    [Column(IsPrimaryKey = true)]
    public int CustomerID { get; set; }
    
    [Column]
    public string Name { get; set; }
}

// 创建DataContext
DataContext db = new DataContext("Data Source=myServer;Initial Catalog=myDB;Integrated Security=SSPI");

// 查询客户
var customers = from c in db.GetTable<Customer>()
                where c.Name.StartsWith("A")
                select c;

foreach (var c in customers)
{
    Console.WriteLine(c.Name);
}

7.2 Entity Framework基本用法

// 定义DbContext
public class MyDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
}

// 查询产品
using (var db = new MyDbContext())
{
    var expensiveProducts = from p in db.Products
                            where p.Price > 100
                            orderby p.Name
                            select p;
    
    foreach (var p in expensiveProducts)
    {
        Console.WriteLine($"{p.Name}: {p.Price}");
    }
}

八、并行LINQ(PLINQ)详解

PLINQ是LINQ的并行版本,可以自动利用多核处理器提高查询性能。

8.1 基本用法

List<int> numbers = Enumerable.Range(1, 1000000).ToList();

// 并行查询偶数
var evenNumbers = numbers.AsParallel()
                        .Where(n => n % 2 == 0)
                        .ToList();

// 并行查询并排序
var sortedNumbers = numbers.AsParallel()
                          .AsOrdered() // 保持原始顺序
                          .Where(n => n > 500000)
                          .OrderBy(n => n)
                          .Take(10)
                          .ToList();

8.2 性能考虑

// 并行度设置
var result = numbers.AsParallel()
                   .WithDegreeOfParallelism(4) // 设置并行度为4
                   .Where(n => n % 2 == 0)
                   .ToList();

// 取消并行查询
var cts = new CancellationTokenSource();
var query = numbers.AsParallel()
                  .WithCancellation(cts.Token)
                  .Where(n => n % 2 == 0);

// 如果需要取消
cts.Cancel();

try
{
    var result = query.ToList();
}
catch (OperationCanceledException)
{
    Console.WriteLine("查询已取消");
}

九、LINQ最佳实践

9.1 查询性能优化

  1. ​延迟执行​​:利用LINQ的延迟执行特性,只在需要时执行查询
  2. ​避免多次枚举​​:将结果转换为列表或数组,如果需要多次使用
  3. ​合理使用并行​​:只在数据量大且计算密集时使用PLINQ
  4. ​选择合适的方法​​:对于简单查询使用方法语法,复杂查询使用查询表达式语法

9.2 代码可读性

  1. ​适当使用换行​​:使复杂查询更易读
  2. ​使用有意义的变量名​​:如expensiveProducts而不是q
  3. ​分解复杂查询​​:将复杂查询拆分为多个简单查询
  4. ​注释复杂逻辑​​:解释复杂的查询条件

9.3 错误处理

  1. ​处理空集合​​:使用DefaultIfEmpty或检查Any()
  2. ​处理异常​​:在查询外部捕获可能的异常
  3. ​验证输入​​:确保查询的数据源有效

十、LINQ实战示例

10.1 数据分析

// 销售数据
List<Sale> sales = new List<Sale>
{
    new Sale { Product = "Laptop", Amount = 1200, Date = new DateTime(2023, 1, 15) },
    new Sale { Product = "Phone", Amount = 800, Date = new DateTime(2023, 1, 20) },
    new Sale { Product = "Laptop", Amount = 1100, Date = new DateTime(2023, 2, 5) },
    new Sale { Product = "Tablet", Amount = 500, Date = new DateTime(2023, 2, 10) },
    new Sale { Product = "Phone", Amount = 850, Date = new DateTime(2023, 3, 1) }
};

// 按月统计销售额
var monthlySales = sales.GroupBy(s => new { s.Date.Year, s.Date.Month })
                        .Select(g => new 
                        { 
                            Year = g.Key.Year, 
                            Month = g.Key.Month, 
                            Total = g.Sum(s => s.Amount) 
                        })
                        .OrderBy(m => m.Year)
                        .ThenBy(m => m.Month);

foreach (var m in monthlySales)
{
    Console.WriteLine($"{m.Year}-{m.Month:D2}: {m.Total:C}");
}

// 找出最畅销的产品
var bestSellingProduct = sales
    .GroupBy(s => s.Product)
    .Select(g => new { Product = g.Key, Total = g.Sum(s => s.Amount) })
    .OrderByDescending(g => g.Total)
    .First();

Console.WriteLine($"Best selling product: {bestSellingProduct.Product} (${bestSellingProduct.Total})");
 

10.2 XML处理

// 创建XML文档
XDocument doc = new XDocument(
    new XDeclaration("1.0", "utf-8", "yes"),
    new XElement("Configuration",
        new XElement("AppSettings",
            new XElement("Add", new XAttribute("Key", "ConnectionString"), new XAttribute("Value", "Server=myServer;Database=myDB;")),
            new XElement("Add", new XAttribute("Key", "Timeout"), new XAttribute("Value", "30"))
        ),
        new XElement("ConnectionStrings",
            new XElement("add", new XAttribute("name", "Default"), new XAttribute("connectionString", "Server=myServer;Database=myDB;"))
        )
    )
);

// 查询特定设置
var connectionString = doc.Descendants("Add")
                         .FirstOrDefault(e => (string)e.Attribute("Key") == "ConnectionString");

if (connectionString != null)
{
    Console.WriteLine($"Connection String: {connectionString.Attribute("Value").Value}");
}

// 添加新设置
doc.Descendants("AppSettings").First().Add(
    new XElement("Add", 
        new XAttribute("Key", "MaxConnections"), 
        new XAttribute("Value", "100")));

doc.Save("config.xml");

10.3 数据库查询

// 使用Entity Framework查询
using (var db = new MyDbContext())
{
    // 查询所有产品
    var allProducts = db.Products.ToList();
    
    // 查询价格大于100的产品并按名称排序
    var expensiveProducts = from p in db.Products
                            where p.Price > 100
                            orderby p.Name
                            select p;
    
    // 分组统计
    var categoryStats = from p in db.Products
                        group p by p.Category into g
                        select new 
                        { 
                            Category = g.Key, 
                            Count = g.Count(), 
                            AveragePrice = g.Average(p => p.Price) 
                        };
    
    // 分页查询
    var pagedProducts = db.Products
                          .OrderBy(p => p.Name)
                          .Skip(20)
                          .Take(10)
                          .ToList();
}

LINQ是C#中强大而灵活的查询技术,它统一了各种数据源的查询方式,使代码更加简洁、易读和可维护。通过本文的介绍,我们学习了:

  1. LINQ的基本语法和操作符
  2. LINQ to Objects的核心功能
  3. LINQ to XML的文档处理能力
  4. LINQ to SQL/Entities的数据库查询
  5. PLINQ的并行查询技术
  6. LINQ的最佳实践和性能优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code_shenbing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值