在 C# 的ADO.NET中,DataTable是内存中的数据表 ,是DataSet的核心组成部分,也可独立使用。它模拟了关系型数据库中 "表" 的结构,包含列定义(DataColumn)、行数据(DataRow)、约束(主键、唯一键等)和数据状态跟踪,是处理结构化数据的基础组件。
一、DataTable 的核心特性
-
结构化存储:包含列(定义数据类型、约束)和行(存储具体数据),类似数据库表的结构。
-
独立使用 :无需依赖
DataSet,可单独创建和操作(如存储查询结果、临时数据)。 -
状态跟踪:自动记录行的修改状态(新增、修改、删除),便于批量更新。
-
数据验证:支持通过列约束(如非空、唯一)和自定义验证确保数据合法性。
二、DataTable 的基本结构
DataTable的核心组成如下:
-
Columns:DataColumnCollection集合,存储表的列定义(字段名、数据类型、约束等)。 -
Rows:DataRowCollection集合,存储表的行数据(每条行对应一条记录)。 -
Constraints:ConstraintCollection集合,存储约束(如主键、外键、唯一键)。 -
PrimaryKey:指定主键列(可由多个列组成复合主键),用于快速查找行。
三、常用属性与方法
| 类别 | 成员 | 说明 |
|---|---|---|
| 属性 | TableName |
获取或设置表名 |
Columns |
获取列集合(DataColumnCollection),用于管理列定义 |
|
Rows |
获取行集合(DataRowCollection),用于管理行数据 |
|
PrimaryKey |
获取或设置主键列数组(DataColumn[]) |
|
DefaultView |
获取DataView(表的视图),用于排序、筛选数据 |
|
| 方法 | NewRow() |
创建与表结构匹配的新行(DataRow),需通过Rows.Add()添加到表中 |
AcceptChanges() |
提交所有行的修改(将RowState重置为Unchanged) |
|
RejectChanges() |
撤销所有未提交的修改(恢复到上次AcceptChanges后的状态) |
|
Select(string) |
根据筛选条件查询行,返回DataRow[] |
|
Compute(string, string) |
计算聚合值(如SUM/COUNT),参数为表达式和筛选条件 |
|
Clear() |
清空所有行数据(保留列结构) |
四、使用示例:完整操作流程
以下通过 "产品表" 示例,展示DataTable的创建、结构定义、数据操作、约束管理等核心功能。
1. 创建 DataTable 并定义列结构
// 1. 创建DataTable(指定表名)
DataTable productTable = new DataTable("Products");
// 2. 定义列(DataColumn)
// 产品ID(主键,int类型,非空)
DataColumn idColumn = new DataColumn("ProductId", typeof(int))
{
AllowDBNull = false, // 不允许为空
Unique = true // 唯一约束(主键依赖此属性)
};
productTable.Columns.Add(idColumn);
// 产品名称(string类型,长度50)
DataColumn nameColumn = new DataColumn("ProductName", typeof(string))
{
MaxLength = 50, // 最大长度
AllowDBNull = false
};
productTable.Columns.Add(nameColumn);
// 价格(decimal类型,默认值0)
DataColumn priceColumn = new DataColumn("Price", typeof(decimal))
{
DefaultValue = 0m // 默认值
};
productTable.Columns.Add(priceColumn);
// 库存(int类型,最小值0)
DataColumn stockColumn = new DataColumn("Stock", typeof(int));
productTable.Columns.Add(stockColumn);
// 3. 设置主键(可多列组成复合主键)
productTable.PrimaryKey = new[] { idColumn };
// 验证结构
Console.WriteLine($"表名:{productTable.TableName}");
Console.WriteLine($"列数量:{productTable.Columns.Count}"); // 输出:4
Console.WriteLine($"主键列:{productTable.PrimaryKey[0].ColumnName}"); // 输出:ProductId
2. 操作行数据(添加、修改、删除)
// 1. 添加行数据
// 方法1:通过NewRow()创建行(推荐,自动匹配列结构)
DataRow row1 = productTable.NewRow();
row1["ProductId"] = 1;
row1["ProductName"] = "笔记本电脑";
row1["Price"] = 5999.99m;
row1["Stock"] = 100;
productTable.Rows.Add(row1);
// 方法2:直接传值(按列顺序,需确保类型匹配)
productTable.Rows.Add(2, "鼠标", 99.99m, 500);
productTable.Rows.Add(3, "键盘", 199.99m, 300);
// 2. 修改行数据
DataRow laptopRow = productTable.Rows.Find(1); // 通过主键查找行
if (laptopRow != null)
{
laptopRow["Price"] = 5799.99m; // 降价
laptopRow["Stock"] = 95; // 减少库存
}
// 3. 删除行(标记删除,需AcceptChanges确认)
DataRow keyboardRow = productTable.Rows.Find(3);
if (keyboardRow != null)
{
productTable.Rows.Remove(keyboardRow); // 直接移除(立即生效)
// 或标记删除:keyboardRow.Delete();(需AcceptChanges后才移除)
}
// 4. 查看行状态(跟踪修改)
foreach (DataRow row in productTable.Rows)
{
Console.WriteLine(
$"ID: {row["ProductId"]}, 名称: {row["ProductName"]}, " +
$"状态: {row.RowState}" // 输出:Modified/Unchanged
);
}
3. 数据查询与计算
// 1. 按条件查询行(Select方法)
// 示例:查询价格>1000的产品
DataRow[] expensiveProducts = productTable.Select("Price > 1000");
Console.WriteLine("价格>1000的产品:");
foreach (DataRow row in expensiveProducts)
{
Console.WriteLine($"{row["ProductName"]} - {row["Price"]}元");
}
// 2. 排序查询(按库存降序)
DataRow[] sortedByStock = productTable.Select("", "Stock DESC"); // 第二个参数为排序表达式
// 3. 聚合计算(Compute方法)
// 示例:计算总库存
object totalStock = productTable.Compute("SUM(Stock)", ""); // 第二个参数为筛选条件(空表示全部)
Console.WriteLine($"总库存:{totalStock}"); // 输出:100+500=600(因键盘已被移除)
// 示例:计算平均价格(仅统计价格>100的产品)
object avgPrice = productTable.Compute("AVG(Price)", "Price > 100");
Console.WriteLine($"平均价格(>100):{avgPrice}"); // 输出:(5799.99)/1=5799.99
4. 数据约束与验证
// 1. 添加唯一约束(确保产品名称不重复)
UniqueConstraint nameUnique = new UniqueConstraint("UK_ProductName", nameColumn);
productTable.Constraints.Add(nameUnique);
// 2. 添加检查约束(库存不能为负数)
CheckConstraint stockCheck = new CheckConstraint("CK_Stock", "Stock >= 0");
productTable.Constraints.Add(stockCheck);
// 3. 测试约束(触发异常)
try
{
// 尝试添加重复名称的产品(违反唯一约束)
productTable.Rows.Add(4, "笔记本电脑", 4999.99m, 50);
}
catch (ConstraintException ex)
{
Console.WriteLine($"约束错误:{ex.Message}"); // 输出:列ProductName的值必须唯一
}
try
{
// 尝试设置库存为负数(违反检查约束)
DataRow mouseRow = productTable.Rows.Find(2);
mouseRow["Stock"] = -10;
}
catch (ConstraintException ex)
{
Console.WriteLine($"约束错误:{ex.Message}"); // 输出:CK_Stock约束失败
}
5. 与数据库交互(结合 DataAdapter)
DataTable常通过DataAdapter与数据库同步,实现数据加载和更新:
string connectionString = "Server=.;Database=ShopDB;Integrated Security=True;";
// 1. 从数据库加载数据到DataTable
using (SqlDataAdapter adapter = new SqlDataAdapter(
"SELECT ProductId, ProductName, Price, Stock FROM Products",
connectionString))
{
DataTable dbProducts = new DataTable();
adapter.Fill(dbProducts); // 自动填充数据并创建列结构
Console.WriteLine($"从数据库加载 {dbProducts.Rows.Count} 条产品数据");
}
// 2. 将修改更新回数据库
using (SqlDataAdapter adapter = new SqlDataAdapter())
{
adapter.SelectCommand = new SqlCommand(
"SELECT ProductId, ProductName, Price, Stock FROM Products",
new SqlConnection(connectionString)
);
// 自动生成增删改命令
SqlCommandBuilder cmdBuilder = new SqlCommandBuilder(adapter);
// 假设已修改内存中的productTable
int updatedRows = adapter.Update(productTable); // 根据RowState批量更新
Console.WriteLine($"成功更新 {updatedRows} 条数据到数据库");
}
6. 绑定到 UI 控件
DataTable可直接绑定到 WinForm/WPF 的 UI 控件(如DataGridView),实现数据展示:
// WinForm示例:绑定到DataGridView
// dataGridView1.DataSource = productTable;
// 或通过DataView绑定(支持排序/筛选)
DataView productView = productTable.DefaultView;
productView.Sort = "Price ASC"; // 按价格升序
productView.RowFilter = "Stock > 0"; // 只显示有库存的产品
// dataGridView1.DataSource = productView;
五、适用场景与注意事项
适用场景:
-
内存中临时存储结构化数据(如查询结果、用户输入的批量数据)。
-
简单数据关系处理 (单表操作,或与其他
DataTable通过DataRelation关联)。 -
UI 数据绑定 (快速将数据展示到
DataGridView等控件)。 -
小规模数据的批量处理(如导入 Excel 数据后在内存中校验和转换)。
注意事项:
-
内存占用 :数据全部加载到内存,不适合处理超大量数据(建议用
DataReader流式读取)。 -
数据类型匹配:添加行时需确保值的类型与列定义一致,否则会抛出类型异常。
-
约束性能:过多约束会影响数据操作性能,复杂验证建议在业务层实现。
-
状态管理 :
AcceptChanges()会清除修改状态,如需后续更新数据库,应在更新后再调用。
六、总结
DataTable是处理内存中结构化数据的核心工具,其设计模拟了关系型数据库表的特性,提供了完整的列定义、行操作、约束管理和状态跟踪能力。无论是独立使用存储临时数据,还是与DataSet、DataAdapter配合实现离线数据处理,DataTable都能简化数据操作流程,是 C# 数据处理中的基础组件。实际开发中,需根据数据量和业务复杂度选择使用,平衡便利性和性能。