C#中DataTable的查询与行操作指南

2026-09-02 20:47:26 5 次阅读

DataTable的基本结构与创建

DataTable 是 C# 中 System.Data 命名空间提供的内存数据表对象,常用于保存查询结果、临时数据以及需要进行筛选、排序和修改的数据集合。它不依赖数据库连接,数据加载到内存后即可直接操作,因此在桌面应用、Web 项目、数据转换以及报表处理中都比较常见。

使用 DataTable 前通常需要引入:

C#
using System.Data;

最简单的创建方式如下:

C#
DataTable table = new DataTable("Users");

table.Columns.Add("Id", typeof(int));
table.Columns.Add("Name", typeof(string));
table.Columns.Add("Age", typeof(int));

table.Rows.Add(1, "张三", 25);
table.Rows.Add(2, "李四", 30);
table.Rows.Add(3, "王五", 28);

此时表结构可以理解为:

IdNameAge
1张三25
2李四30
3王五28

Columns 用于管理列,Rows 用于管理数据行。实际开发中,查询和行操作基本都围绕这两个集合展开。

使用 Select 查询DataTable中的数据

DataTable.Select() 是进行条件查询最直接的方法,可以按照类似 SQL WHERE 子句的表达式筛选数据。

例如查询年龄大于 25 岁的数据:

C#
DataRow[] rows = table.Select("Age > 25");

foreach (DataRow row in rows)
{
    Console.WriteLine($"{row["Id"]} - {row["Name"]} - {row["Age"]}");
}

也可以组合多个条件:

C#
DataRow[] rows = table.Select("Age >= 25 AND Name <> '张三'");

字符串条件需要使用单引号:

C#
DataRow[] rows = table.Select("Name = '李四'");

如果条件中的字符串来自用户输入,应避免直接拼接表达式,否则可能出现特殊字符导致查询表达式异常。对于复杂业务条件,更适合先使用 LINQ 查询。

DataTable按条件查询单行

如果明确知道条件最多只会匹配一条记录,可以获取数组后判断:

C#
DataRow[] rows = table.Select("Id = 2");

if (rows.Length > 0)
{
    DataRow row = rows[0];

    Console.WriteLine(row["Name"]);
}

需要注意,Select() 即使没有匹配结果,也不会返回 null,而是返回长度为 0 的 DataRow[]

因此不要直接写:

C#
DataRow row = table.Select("Id = 100")[0];

如果不存在 Id 为 100 的数据,就会抛出 IndexOutOfRangeException

使用LINQ查询DataTable

对于比较复杂的筛选逻辑,LINQ 通常比 Select() 更灵活。

C#
using System.Linq;

var result = table.AsEnumerable()
                  .Where(row => row.Field<int>("Age") >= 25);

foreach (DataRow row in result)
{
    Console.WriteLine(row.Field<string>("Name"));
}

这里的 AsEnumerable() 可以将 DataTable 转换为可进行 LINQ 操作的数据序列。

Field() 相比直接使用:

C#
row["Age"]

具有更明确的类型信息。

例如:

C#
int age = row.Field<int>("Age");
string name = row.Field<string>("Name");

如果数据库字段可能存在 DBNull,则应使用可空类型:

C#
int? age = row.Field<int?>("Age");

这种方式可以避免直接转换 DBNull.Value 引发异常。

查询DataTable中的指定列

如果只需要获取某一列,可以结合 LINQ:

C#
var names = table.AsEnumerable()
                 .Select(row => row.Field<string>("Name"));

foreach (string name in names)
{
    Console.WriteLine(name);
}

也可以同时选择多个字段:

C#
var users = table.AsEnumerable()
                 .Select(row => new
                 {
                     Id = row.Field<int>("Id"),
                     Name = row.Field<string>("Name"),
                     Age = row.Field<int>("Age")
                 });

foreach (var user in users)
{
    Console.WriteLine($"{user.Id} {user.Name} {user.Age}");
}

这种方式适合后续需要继续排序、分组或转换数据的场景。

DataTable排序查询

Select() 本身支持第二个参数指定排序表达式:

C#
DataRow[] rows = table.Select("", "Age DESC");

第一个参数为空字符串表示不设置筛选条件,第二个参数按照年龄降序排列。

也可以同时排序:

C#
DataRow[] rows = table.Select("", "Age DESC, Name ASC");

表示先按照 Age 降序排列,年龄相同的情况下再按照 Name 升序排列。

使用 LINQ 时则可以写成:

C#
var result = table.AsEnumerable()
                  .OrderByDescending(row => row.Field<int>("Age"))
                  .ThenBy(row => row.Field<string>("Name"));

添加DataRow的方法

DataTable 添加数据最简单的方法是直接使用 Rows.Add()

C#
table.Rows.Add(4, "赵六", 35);

如果需要更加明确地操作每一个字段,可以创建 DataRow

C#
DataRow newRow = table.NewRow();

newRow["Id"] = 5;
newRow["Name"] = "孙七";
newRow["Age"] = 26;

table.Rows.Add(newRow);

NewRow() 会按照当前 DataTable 的结构创建一个新的 DataRow,适合字段较多或者需要动态赋值的场景。

修改DataRow中的数据

获取到 DataRow 后,可以直接通过列名修改:

C#
DataRow[] rows = table.Select("Id = 2");

if (rows.Length > 0)
{
    rows[0]["Age"] = 31;
}

也可以使用 LINQ 找到目标行:

C#
DataRow? row = table.AsEnumerable()
                    .FirstOrDefault(r => r.Field<int>("Id") == 2);

if (row != null)
{
    row["Age"] = 31;
}

修改后,DataTable 中对应的行会立即发生变化。

删除DataTable中的数据行

删除指定行有多种方式。

直接通过索引删除:

C#
table.Rows.RemoveAt(0);

如果已经拿到了 DataRow,可以:

C#
DataRow[] rows = table.Select("Id = 3");

if (rows.Length > 0)
{
    table.Rows.Remove(rows[0]);
}

还可以调用:

C#
rows[0].Delete();

不过 Delete()Remove() 在行为上存在区别。

Delete() 会将行状态标记为 Deleted,特别是在 DataAdapter 与数据源同步时,这种状态具有实际意义。Rows.Remove() 则会直接将行从 DataTable.Rows 集合中移除。

因此,如果 DataTable 后续还需要通过 DataAdapter.Update() 同步数据库,应根据具体的数据更新流程选择删除方式。

批量删除满足条件的数据

如果需要删除多个符合条件的行,不建议在遍历 Rows 的同时直接删除当前集合元素。

例如可以先查询:

C#
DataRow[] rows = table.Select("Age < 25");

foreach (DataRow row in rows)
{
    row.Delete();
}

如果不需要保留 Deleted 状态,也可以直接:

C#
foreach (DataRow row in rows)
{
    table.Rows.Remove(row);
}

这样可以避免直接修改 Rows 集合时产生遍历问题。

获取DataTable的行数

获取当前行数非常简单:

C#
int count = table.Rows.Count;

例如:

C#
if (table.Rows.Count == 0)
{
    Console.WriteLine("没有数据");
}
else
{
    Console.WriteLine($"共有 {table.Rows.Count} 条数据");
}

需要注意,如果表中存在已经被标记为 Deleted 的行,某些场景下直接使用 Rows.Count 与业务意义上的“有效数据数量”并不完全相同。涉及数据修改状态时,应结合 DataRowState 判断。

判断DataTable是否存在指定数据

如果只是判断是否存在某条记录,没有必要把所有数据都查询出来。

LINQ 可以使用 Any()

C#
bool exists = table.AsEnumerable()
                   .Any(row => row.Field<int>("Id") == 2);

if (exists)
{
    Console.WriteLine("记录存在");
}

如果使用 Select()

C#
bool exists = table.Select("Id = 2").Length > 0;

对于只关心“有没有”的场景,Any() 表达意图更加直接。

遍历DataTable所有行

最常见的遍历方式是:

C#
foreach (DataRow row in table.Rows)
{
    Console.WriteLine(row["Name"]);
}

如果需要遍历所有列:

C#
foreach (DataRow row in table.Rows)
{
    foreach (DataColumn column in table.Columns)
    {
        Console.Write($"{row[column]} ");
    }

    Console.WriteLine();
}

这种方式适合字段结构不固定的动态数据处理。

判断DataRow中的DBNull

DataTable 中的数据库空值通常表示为 DBNull.Value,而不是 C# 的 null

错误示例:

C#
string name = row["Name"].ToString();

虽然 ToString() 在很多情况下不会直接报错,但无法明确表达字段是否真正存在数据库空值。

更稳妥的写法:

C#
if (row.IsNull("Name"))
{
    Console.WriteLine("Name为空");
}
else
{
    Console.WriteLine(row["Name"]);
}

也可以:

C#
string name = row.Field<string>("Name") ?? "";

如果字段类型是整数:

C#
int? age = row.Field<int?>("Age");

复制DataTable与筛选数据

DataTable.Copy() 可以复制表结构和数据:

C#
DataTable copyTable = table.Copy();

如果只需要复制结构:

C#
DataTable structureTable = table.Clone();

两者区别很重要:

  • Clone():复制表结构,不复制数据。

  • Copy():复制表结构,同时复制数据。

例如:

C#
DataTable newTable = table.Clone();

DataRow[] rows = table.Select("Age >= 30");

foreach (DataRow row in rows)
{
    newTable.ImportRow(row);
}

这样可以创建一个只包含符合条件数据的新表。

使用DataTable.Select进行分页

对于内存中的 DataTable,可以利用 Select() 的排序结果结合 LINQ 实现简单分页:

C#
var pageData = table.AsEnumerable()
                    .OrderBy(row => row.Field<int>("Id"))
                    .Skip(10)
                    .Take(10);

foreach (DataRow row in pageData)
{
    Console.WriteLine(row.Field<string>("Name"));
}

这里表示跳过前 10 条,再获取 10 条数据,也就是常见的第二页数据。

不过需要注意,DataTable 分页属于内存分页。如果数据本身来自数据库,并且数据量很大,更推荐直接使用数据库的分页查询,而不是先将全部数据加载到内存。

DataTable查询操作的选择

不同需求可以采用不同方式:

操作需求推荐方式
简单条件筛选Select()
多条件动态查询LINQ
判断是否存在Any()
获取第一条数据FirstOrDefault()
排序Select() 或 LINQ
批量转换LINQ
添加数据Rows.Add() / NewRow()
修改数据DataRow 索引器
删除数据Delete() / Remove()
复制结构Clone()
复制数据Copy()

实际项目中不必拘泥于某一种方法。简单查询使用 Select() 足够直接;当筛选条件、排序、投影和分组逐渐复杂时,LINQ 通常更容易维护。

常见问题与性能注意事项

DataTable 的优势是使用方便,但它本质上是内存数据结构,并不适合无限制地保存大量数据。

如果数据库中有几十万甚至数百万条记录,先执行完整查询,再把所有结果装进 DataTable,可能导致较高的内存占用和较长的处理时间。更合理的方案通常是在数据库层完成 WHERE、排序和分页,只把实际需要的数据返回给应用程序。

此外,频繁执行:

C#
table.Select("Id = 100");

虽然使用简单,但如果需要针对大量记录反复进行复杂查找,可以考虑优化数据访问方式,例如建立合适的数据索引、使用 LINQ 的合理查询策略,或者直接让数据库完成查询。

修改行时也要注意数据类型:

C#
row["Age"] = "30";

如果 Age 定义为 int,这种赋值可能导致类型转换问题。推荐直接使用正确的数据类型:

C#
row["Age"] = 30;

对于来自用户输入的字符串,则应先进行安全转换:

C#
if (int.TryParse(inputAge, out int age))
{
    row["Age"] = age;
}

一个完整的DataTable行操作示例

下面将查询、添加、修改和删除操作组合起来:

C#
using System;
using System.Data;
using System.Linq;

class Program
{
    static void Main()
    {
        DataTable table = new DataTable("Users");

        table.Columns.Add("Id", typeof(int));
        table.Columns.Add("Name", typeof(string));
        table.Columns.Add("Age", typeof(int));

        // 添加数据
        table.Rows.Add(1, "张三", 25);
        table.Rows.Add(2, "李四", 30);
        table.Rows.Add(3, "王五", 28);

        // 条件查询
        DataRow[] rows = table.Select("Age >= 28");

        foreach (DataRow row in rows)
        {
            Console.WriteLine(
                $"{row["Id"]} {row["Name"]} {row["Age"]}");
        }

        // 修改数据
        DataRow? user = table.AsEnumerable()
                             .FirstOrDefault(
                                 r => r.Field<int>("Id") == 1);

        if (user != null)
        {
            user["Age"] = 26;
        }

        // 添加一条数据
        DataRow newRow = table.NewRow();
        newRow["Id"] = 4;
        newRow["Name"] = "赵六";
        newRow["Age"] = 32;

        table.Rows.Add(newRow);

        // 删除数据
        DataRow[] deleteRows = table.Select("Id = 3");

        foreach (DataRow row in deleteRows)
        {
            row.Delete();
        }

        // 输出最终数据
        foreach (DataRow row in table.Rows)
        {
            if (row.RowState != DataRowState.Deleted)
            {
                Console.WriteLine(
                    $"{row["Id"]} {row["Name"]} {row["Age"]}");
            }
        }
    }
}

这个示例涵盖了 DataTable 最常见的基本操作:创建表结构、添加 DataRow、条件查询、LINQ 查询、修改字段以及删除数据。

掌握 DataTable.Select()DataTable.AsEnumerable()DataRowRows.Add()Delete()Clone()Copy() 等核心 API 后,就可以应对大多数 C# 内存表数据处理需求。对于简单的数据筛选,优先考虑代码直观的 Select();对于复杂条件、排序和数据转换,则可以使用 LINQ;如果数据规模较大,则应尽可能将查询和分页下沉到数据库层完成。