目录
- 什么是 LINQ to DataSet?
- 准备工作:环境配置
- 核心概念:
AsEnumerable()方法 - 基本查询操作
- 1 查询所有行 (
Select) - 2 筛选数据 (
Where) - 3 排序数据 (
OrderBy,ThenBy) - 4 分组数据 (
GroupBy) - 5 投影数据 (
Select的进阶用法)
- 1 查询所有行 (
- 高级查询操作
- 1 聚合函数 (
Count,Sum,Average,Max,Min) - 2 连接查询 (
Join,GroupJoin) - 3 集合操作 (
Union,Intersect,Except)
- 1 聚合函数 (
- 处理
DataRow对象 - 性能优化技巧
- 完整示例:从数据库到查询结果
什么是 LINQ to DataSet?
LINQ to DataSet 是 LINQ to Objects 的一个特例,它允许你使用强大的 LINQ 查询语法来查询和操作 ADO.NET DataSet 和 DataTable 中的数据。

核心优势:
- 声明式语法:使用类似 SQL 的
Where,Select,OrderBy等关键字,代码更易读、更易维护。 - 编译时检查:查询在编译时进行检查,可以提前发现错误,而不是等到运行时。
- 丰富的功能:除了基本的查询,还支持聚合、连接、分组等复杂操作。
- 与现有 ADO.NET 代码无缝集成:可以轻松地将 LINQ 查询结果与传统的 ADO.NET 代码结合使用。
与传统 DataView 或 DataTable.Select() 的对比:
DataTable.Select():功能有限,语法笨拙,不支持复杂的排序和分组。DataView:主要用于绑定和排序,查询能力较弱。- LINQ to DataSet:功能最强大,语法最优雅,是处理内存中
DataSet数据的首选方式。
准备工作:环境配置
要使用 LINQ to DataSet,你的项目需要满足以下条件:
-
.NET Framework:LINQ 是 .NET Framework 3.5 引入的,因此你的项目目标框架必须是 3.5 或更高版本。
(图片来源网络,侵删) -
必要的命名空间:在你的代码文件顶部(如
using或Imports)添加以下引用:using System.Data; // 用于 DataSet, DataTable, DataRow 等 using System.Data.SqlClient; // 如果使用 SQL Server using System.Linq; // LINQ 的核心命名空间 using System.Linq.Expressions; // 有时用于高级查询
核心概念:AsEnumerable() 方法
这是 LINQ to DataSet 中最重要的一个方法。
DataTable 本身并不直接实现 IEnumerable<T> 接口,因此不能直接对其使用 LINQ 查询。AsEnumerable() 方法的作用就是将 DataTable 转换为一个 IEnumerable<DataRow> 序列,一旦你有了这个序列,你就可以对其应用所有标准的 LINQ 操作。
语法:
// 将 DataTable 转换为可查询的 DataRow 序列
IEnumerable<DataRow> query = from row in myTable.AsEnumerable()
// ... 在这里添加查询子句
select row;
基本查询操作
我们假设有一个名为 Products 的 DataTable,其结构如下:
| ProductID | ProductName | CategoryID | UnitPrice | UnitsInStock |
|---|---|---|---|---|
| 1 | Chair | 1 | 00 | 100 |
| 2 | Table | 1 | 00 | 50 |
| 3 | Keyboard | 2 | 00 | 200 |
| 4 | Mouse | 2 | 00 | 500 |
| 5 | Monitor | 3 | 00 | 30 |
1 查询所有行 (Select)
虽然 Select 在简单查询中是可选的(默认行为),但明确使用它可以清晰地表明你的意图,并且是进行数据投影的关键。
// 查询所有产品,并返回 DataRow 对象
var allProducts = from product in productsTable.AsEnumerable()
select product;
// 遍历结果
foreach (var row in allProducts)
{
Console.WriteLine($"ID: {row["ProductID"]}, Name: {row["ProductName"]}");
}
2 筛选数据 (Where)
这是最常见的查询操作,用于根据指定条件过滤行。
// 查询所有单价大于 100 的产品
var expensiveProducts = from product in productsTable.AsEnumerable()
where product.Field<decimal>("UnitPrice") > 100
select product;
foreach (var row in expensiveProducts)
{
Console.WriteLine($"Name: {row["ProductName"]}, Price: {row["UnitPrice"]}");
}
注意:推荐使用 Field<T>() 方法来访问列值,它比直接使用 row["ColumnName"] 更安全,因为它:
- 提供了编译时类型检查。
- 正确处理数据库中的
NULL值,避免NullReferenceException。
3 排序数据 (OrderBy, ThenBy)
使用 OrderBy 进行升序排序,使用 ThenBy 进行次级排序。
// 查询所有产品,按单价升序排序
var productsSortedByPrice = from product in productsTable.AsEnumerable()
orderby product.Field<decimal>("UnitPrice") ascending
select product;
// 查询所有产品,先按类别升序,再按单价降序排序
var productsSortedByCategoryAndPrice = from product in productsTable.AsEnumerable()
orderby product.Field<int>("CategoryID") ascending,
product.Field<decimal>("UnitPrice") descending
select product;
foreach (var row in productsSortedByCategoryAndPrice)
{
Console.WriteLine($"Category: {row["CategoryID"]}, Name: {row["ProductName"]}, Price: {row["UnitPrice"]}");
}
4 分组数据 (GroupBy)
GroupBy 将数据按指定的键进行分组,返回一个 IGrouping<TKey, TElement> 的集合。
// 按类别ID对产品进行分组
var productsByCategory = from product in productsTable.AsEnumerable()
group product by product.Field<int>("CategoryID");
foreach (var group in productsByCategory)
{
Console.WriteLine($"Category ID: {group.Key}");
foreach (var product in group)
{
Console.WriteLine($" - {product["ProductName"]}");
}
}
5 投影数据 (Select 的进阶用法)
查询结果不一定非要是 DataRow,你可以使用 Select 将数据投影到新的类型中,例如匿名类型、自定义类或简单类型,这使得数据更易于在 UI 层或服务层使用。
使用匿名类型:
// 查询产品名称和单价,并将结果投影为匿名类型
var productDetails = from product in productsTable.AsEnumerable()
select new
{
Name = product.Field<string>("ProductName"),
Price = product.Field<decimal>("UnitPrice")
};
foreach (var detail in productDetails)
{
Console.WriteLine($"Product: {detail.Name}, Price: {detail.Price:C}");
}
使用自定义类:
public class ProductInfo
{
public string Name { get; set; }
public decimal Price { get; set; }
}
// 查询并投影到自定义类
var productInfos = from product in productsTable.AsEnumerable()
select new ProductInfo
{
Name = product.Field<string>("ProductName"),
Price = product.Field<decimal>("UnitPrice")
};
foreach (var info in productInfos)
{
Console.WriteLine($"Product: {info.Name}, Price: {info.Price:C}");
}
高级查询操作
1 聚合函数
LINQ 提供了丰富的聚合函数。
// 查询产品总数
int totalProducts = productsTable.AsEnumerable().Count();
Console.WriteLine($"Total Products: {totalProducts}");
// 计算所有产品的总库存
int totalStock = productsTable.AsEnumerable().Sum(p => p.Field<int>("UnitsInStock"));
Console.WriteLine($"Total Stock: {totalStock}");
// 计算平均单价
decimal averagePrice = productsTable.AsEnumerable().Average(p => p.Field<decimal>("UnitPrice"));
Console.WriteLine($"Average Price: {averagePrice:C}");
// 找出最贵的产品
var mostExpensiveProduct = productsTable.AsEnumerable().OrderByDescending(p => p.Field<decimal>("UnitPrice")).FirstOrDefault();
Console.WriteLine($"Most Expensive: {mostExpensiveProduct["ProductName"]}");
2 连接查询 (Join, GroupJoin)
假设我们还有一个 Categories 表。
| CategoryID | CategoryName |
|---|---|
| 1 | Furniture |
| 2 | Electronics |
| 3 | Computers |
// 假设我们有两个 DataTable: productsTable 和 categoriesTable
var productsWithCategories = from product in productsTable.AsEnumerable()
join category in categoriesTable.AsEnumerable()
on product.Field<int>("CategoryID") equals category.Field<int>("CategoryID")
select new
{
ProductName = product.Field<string>("ProductName"),
CategoryName = category.Field<string>("CategoryName"),
Price = product.Field<decimal>("UnitPrice")
};
foreach (var item in productsWithCategories)
{
Console.WriteLine($"{item.ProductName} ({item.CategoryName}) - {item.Price:C}");
}
3 集合操作
这些操作需要两个集合。
// 假设我们有另一个 DataTable: discontinuedProductsTable
// 获取所有产品,但排除已停产的产品
var availableProducts = productsTable.AsEnumerable()
.Except(discontinuedProductsTable.AsEnumerable(), DataRowComparer.Default);
// 获取既在主产品列表又在促销列表中的产品 (交集)
var promotionalProducts = productsTable.AsEnumerable()
.Intersect(promotionalProductsTable.AsEnumerable(), DataRowComparer.Default);
// 获取所有产品,并合并促销列表中的新产品 (并集)
var allProductsCombined = productsTable.AsEnumerable()
.Union(promotionalProductsTable.AsEnumerable(), DataRowComparer.Default);
注意:DataRowComparer.Default 用于比较 DataRow 对象,因为它需要比较所有列的值。
处理 DataRow 对象
当你查询返回的是 DataRow 时,你可能需要修改它们。DataRow 是一个引用类型,所以当你修改查询结果中的 DataRow 时,你实际上是在修改原始 DataTable 中的数据。
// 查询所有单价低于 50 的产品,并将它们的价格提高 10%
var cheapProducts = from product in productsTable.AsEnumerable()
where product.Field<decimal>("UnitPrice") < 50
select product;
foreach (var product in cheapProducts)
{
decimal newPrice = product.Field<decimal>("UnitPrice") * 1.1m;
product.SetField("UnitPrice", newPrice); // 修改原始数据
}
// productsTable 中的数据已经被永久修改了
性能优化技巧
-
使用
AsEnumerable():始终使用它来启动查询。 -
尽早筛选:在查询链的早期使用
Where子句,可以减少后续操作需要处理的数据量。// 好:先筛选,再排序 var query = from p in productsTable.AsEnumerable() where p.Field<bool>("IsActive") // 先过滤掉不活跃的 orderby p.Field<string>("Name") select p; // 不好:先排序,再筛选 var query = from p in productsTable.AsEnumerable() orderby p.Field<string>("Name") where p.Field<bool>("IsActive") // 排序时所有数据都在内存中 select p; -
考虑使用
DataTable.Copy():如果你需要对原始DataTable进行大量修改,但又不希望影响原始数据,可以先创建一个副本,然后在副本上进行查询和修改。DataTable workingCopy = productsTable.Copy(); // 在 workingCopy 上进行 LINQ 操作和修改
-
避免在循环中查询:不要在
foreach循环内部对同一个DataTable执行 LINQ 查询,应该一次性获取所需数据。
完整示例:从数据库到查询结果
这个示例展示了如何从数据库填充 DataSet,然后使用 LINQ to DataSet 进行查询。
using System;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
public class LinqToDataSetExample
{
public static void Main()
{
// 1. 连接数据库并填充 DataSet
string connectionString = "Your_Connection_String";
string query = "SELECT ProductID, ProductName, CategoryID, UnitPrice, UnitsInStock FROM Products; SELECT CategoryID, CategoryName FROM Categories;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlDataAdapter adapter = new SqlDataAdapter(query, connection);
DataSet dataSet = new DataSet();
adapter.Fill(dataSet);
// 获取 DataTable
DataTable productsTable = dataSet.Tables[0];
DataTable categoriesTable = dataSet.Tables[1];
Console.WriteLine("--- 查询所有单价大于 100 的产品 ---");
var expensiveProducts = from p in productsTable.AsEnumerable()
where p.Field<decimal>("UnitPrice") > 100
orderby p.Field<decimal>("UnitPrice") descending
select new
{
Name = p.Field<string>("ProductName"),
Price = p.Field<decimal>("UnitPrice")
};
foreach (var p in expensiveProducts)
{
Console.WriteLine($"{p.Name}: {p.Price:C}");
}
Console.WriteLine("\n--- 按类别分组产品并计算每个类别的产品数量 ---");
var categoryGroups = from p in productsTable.AsEnumerable()
join c in categoriesTable.AsEnumerable()
on p.Field<int>("CategoryID") equals c.Field<int>("CategoryID")
group p by c.Field<string>("CategoryName") into g
select new
{
CategoryName = g.Key,
ProductCount = g.Count()
};
foreach (var group in categoryGroups)
{
Console.WriteLine($"{group.CategoryName}: {group.ProductCount} 个产品");
}
}
}
}
LINQ to DataSet 是一个极其强大和优雅的工具,用于处理内存中的 ADO.NET 数据,通过掌握 AsEnumerable() 和 LINQ 的标准查询操作符,你可以以声明式、类型安全且富有表现力的方式完成各种复杂的数据检索、筛选、排序、分组和聚合任务。
关键要点回顾:
- 入口是
AsEnumerable():没有它,LINQ 查询无法工作。 - 优先使用
Field<T>():为了类型安全和处理NULL。 Select用于投影:让查询结果更符合你的业务逻辑。- 组合使用:
Where,OrderBy,GroupBy等可以自由组合,构建复杂的查询。 - 性能至上:尽早
Where,减少数据集大小。
希望这份详尽的教程能帮助你顺利上手 LINQ to DataSet!
