C# DataTableの使い方完全ガイド|作成・追加・検索・更新・削除をサンプルで解説

はじめに

C#で表形式のデータを扱うときに便利なのが、DataTableクラスです。データベースから取得した結果を一時的に保持したり、複数のデータを検索・並べ替え・集計したりする場面で広く利用されています。

DataTableには、列の定義、行の追加、条件検索、更新、削除といった、データベースのテーブルに近い操作が用意されています。一方で、値の取得方法やNULLの扱い、Selectメソッドの条件式など、初めて使う人が迷いやすいポイントも少なくありません。

この記事では、C#のDataTableについて、作成からデータの追加、取得、検索、並べ替え、更新までをサンプルコード付きで解説します。

コード例では、主に次の名前空間を使用します。

using System;using System.Data;using System.Linq;

1. C#のDataTableとは

1-1. DataTableの基本概念と主な用途

DataTableは、メモリ上で表形式のデータを管理するためのクラスです。データベースのテーブルと同じように、列と行でデータを表現します。

たとえば、社員情報をDataTableで管理する場合、次のような構造になります。

IdNameDepartmentAge
1佐藤営業部28
2鈴木開発部35
3高橋総務部42

DataTableでは、列をDataColumn、行をDataRowとして扱います。

主な用途は次のとおりです。

  • データベースから取得した検索結果の保持

  • CSVやExcelから読み込んだデータの一時保存

  • 表形式データの検索、並べ替え、集計

  • Windows FormsやWPFの画面へのデータ表示

  • 複数のデータソース間でのデータ受け渡し

  • バッチ処理や帳票作成用データの加工

DataTableはデータベースそのものではなく、アプリケーションのメモリ上に作成されるオブジェクトです。そのため、DataTableの内容を変更しても、自動的にデータベースへ反映されるわけではありません。

1-2. DataTable・DataSet・DataViewの違い

DataTableと一緒に使われることが多いクラスとして、DataSetDataViewがあります。

それぞれの役割は次のとおりです。

クラス役割
DataTable1つの表形式データを保持する
DataSet複数のDataTableとテーブル間の関連を管理する
DataViewDataTableのデータを絞り込み、並べ替えて表示する

DataSetには複数のDataTableを格納できます。

DataSet dataSet = new DataSet();

DataTable employeeTable = new DataTable("Employees");DataTable departmentTable = new DataTable("Departments");

dataSet.Tables.Add(employeeTable);dataSet.Tables.Add(departmentTable);

一方、DataViewは既存のDataTableを直接変更せず、表示対象の行や順序を切り替えるために使います。

DataView view = new DataView(employeeTable);view.RowFilter = "Age >= 30";view.Sort = "Age DESC";

1つの表を扱うだけならDataTable、複数の表をまとめて扱うならDataSet、表示用に絞り込みや並べ替えを行うならDataViewが適しています。

1-3. Listや配列との違い

DataTableとList<T>、配列には、データ構造や型の扱いに違いがあります。

配列は、同じ型の値を固定長で保持するのに適しています。

string[] names = { "佐藤", "鈴木", "高橋" };

List<T>は、同じ型のオブジェクトを可変長で管理するのに適しています。

List<string> names = new List<string>{"佐藤","鈴木","高橋"};

複数の項目を持つデータをListで管理する場合は、専用クラスを定義するのが一般的です。

public class Employee{public int Id { get; set; }public string Name { get; set; } = string.Empty;public int Age { get; set; }}
List<Employee> employees = new List<Employee>{new Employee { Id = 1, Name = "佐藤", Age = 28 },new Employee { Id = 2, Name = "鈴木", Age = 35 }};

DataTableは、実行時に列構成を変更できることや、文字列の条件式による検索、DataViewとの連携ができる点が特徴です。

比較項目DataTableList<T>配列
表形式データ得意クラス定義が必要あまり向かない
型安全性比較的低い高い高い
列の動的な追加可能原則として不可不可
条件検索SelectやLINQLINQLINQ
データバインド対応しやすい対応可能用途が限定的
データベースとの連携得意変換が必要な場合がある不向き

アプリケーション内でデータ構造が決まっている場合は、型安全なList<T>のほうが扱いやすいことがあります。データベースの検索結果や列構成が動的に変化するデータには、DataTableが適しています。

1-4. DataTableが適しているケース

DataTableは、次のようなケースに適しています。

まず、データベースから取得した結果をそのまま表形式で保持したいケースです。SqlDataAdapterなどを使うと、検索結果をDataTableへ格納できます。

DataTable table = new DataTable();

// SqlDataAdapter adapter = new SqlDataAdapter(sql, connection);// adapter.Fill(table);

また、列構成が実行時まで確定しないケースにも向いています。DataTableでは、プログラムの実行中に列を追加できます。

DataTable table = new DataTable();

table.Columns.Add("Column1", typeof(string));table.Columns.Add("Column2", typeof(int));

そのほか、CSVデータの加工、帳票用データの生成、複数条件による絞り込み、データバインドなどにも便利です。

ただし、大量データをすべてDataTableへ読み込むと、メモリ使用量が増加します。数十万件以上のデータを扱う場合は、データベース側で必要な行だけを絞り込む、DataReaderで順次処理するなどの方法も検討しましょう。

1-5. DataTableを使うための準備

DataTableを利用するには、System.Data名前空間を読み込みます。

using System.Data;

LINQやField<T>メソッドを利用する場合は、System.Linqも読み込みます。

using System.Linq;

.NETの一般的なコンソールアプリケーションでは、そのままDataTableを利用できます。

最小構成のコードは次のとおりです。

using System;using System.Data;

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

    Console.WriteLine("DataTableを作成しました。");}

}

2. C#でDataTableを作成する方法

2-1. DataTableを初期化する

DataTableは、new DataTable()で初期化します。

DataTable table = new DataTable();

テーブル名を指定して初期化することもできます。

DataTable table = new DataTable("Employees");

テーブル名は、DataSetに複数のDataTableを格納するときや、デバッグ時にテーブルを識別するときに役立ちます。

Console.WriteLine(table.TableName);

実行結果は次のとおりです。

Employees

2-2. Columns.Addで列を追加する

DataTableを作成した直後は、列が存在しません。Columns.Addメソッドを使って、必要な列を追加します。

DataTable table = new DataTable("Employees");

table.Columns.Add("Id");table.Columns.Add("Name");table.Columns.Add("Department");

データ型を省略すると、列の型は基本的にstringになります。

列数はColumns.Countで確認できます。

Console.WriteLine(table.Columns.Count);

実行結果は次のとおりです。

3

列を取得するときは、列番号または列名を指定します。

DataColumn firstColumn = table.Columns[0];DataColumn nameColumn = table.Columns["Name"];

Console.WriteLine(firstColumn.ColumnName);Console.WriteLine(nameColumn.DataType);

2-3. 列名とデータ型を指定する

DataTableの列には、列名とデータ型を指定できます。

DataTable table = new DataTable("Employees");

table.Columns.Add("Id", typeof(int));table.Columns.Add("Name", typeof(string));table.Columns.Add("Age", typeof(int));table.Columns.Add("HireDate", typeof(DateTime));table.Columns.Add("IsActive", typeof(bool));

よく使用するデータ型は次のとおりです。

C#の型用途
string名前、住所、コードなどの文字列
intID、年齢、数量などの整数
decimal金額や精度が必要な小数
double計算用の浮動小数点数
DateTime日付と時刻
booltrueまたはfalseの状態

列の型と一致しない値を追加すると、変換できない場合に例外が発生します。

table.Rows.Add(1, "佐藤", 28, new DateTime(2022, 4, 1), true);

int型の列に数値へ変換できない文字列を追加すると、エラーになります。

// Age列がint型なので例外が発生するtable.Rows.Add(2, "鈴木", "年齢不明", DateTime.Today, true);

2-4. 主キーを設定する

主キーとは、各行を一意に識別するための列です。DataTableでは、PrimaryKeyプロパティにDataColumnの配列を設定します。

DataTable table = new DataTable("Employees");

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

table.PrimaryKey = new[]{table.Columns["Id"]};

主キーを設定すると、同じ値を持つ行を複数追加できなくなります。

table.Rows.Add(1, "佐藤");

// 同じIdなので例外が発生する// table.Rows.Add(1, "鈴木");

また、主キーを設定しておくと、Rows.Findメソッドで高速に行を検索できます。

DataRow? row = table.Rows.Find(1);

if (row != null){Console.WriteLine(row["Name"]);}

複合主キーを設定する場合は、複数の列を指定します。

table.PrimaryKey = new[]{table.Columns["DepartmentId"],table.Columns["EmployeeNumber"]};

複合主キーを検索する場合は、キー値を配列で渡します。

DataRow? row = table.Rows.Find(new object[] { 10, 1001 });

2-5. NULL・既定値・一意制約を設定する

DataColumnには、NULLの許可、既定値、一意制約などを設定できます。

NULLを禁止する場合は、AllowDBNullfalseにします。

DataColumn nameColumn = table.Columns.Add("Name", typeof(string));nameColumn.AllowDBNull = false;

値が指定されなかったときの既定値は、DefaultValueで設定します。

DataColumn isActiveColumn = table.Columns.Add("IsActive", typeof(bool));isActiveColumn.DefaultValue = true;

列の値を一意にする場合は、Uniquetrueにします。

DataColumn emailColumn = table.Columns.Add("Email", typeof(string));emailColumn.Unique = true;

IDを自動採番する場合は、AutoIncrementを利用できます。

DataColumn idColumn = table.Columns.Add("Id", typeof(int));

idColumn.AutoIncrement = true;idColumn.AutoIncrementSeed = 1;idColumn.AutoIncrementStep = 1;

設定後は、Idを指定せずに行を追加できます。

table.Rows.Add(null, "佐藤");table.Rows.Add(null, "鈴木");

制約を設定することで、不正なデータがDataTableへ追加されるのを防げます。

2-6. サンプルで使用するDataTableを作成する

ここからのサンプルでは、次のメソッドで作成したDataTableを使用します。

static DataTable CreateEmployeeTable(){DataTable table = new DataTable("Employees");

DataColumn idColumn = table.Columns.Add("Id", typeof(int));idColumn.AllowDBNull = false;idColumn.Unique = true;DataColumn nameColumn = table.Columns.Add("Name", typeof(string));nameColumn.AllowDBNull = false;DataColumn departmentColumn =table.Columns.Add("Department", typeof(string));departmentColumn.AllowDBNull = true;DataColumn ageColumn = table.Columns.Add("Age", typeof(int));ageColumn.AllowDBNull = false;DataColumn salaryColumn =table.Columns.Add("Salary", typeof(decimal));salaryColumn.AllowDBNull = false;salaryColumn.DefaultValue = 0m;DataColumn hireDateColumn =table.Columns.Add("HireDate", typeof(DateTime));hireDateColumn.AllowDBNull = false;DataColumn isActiveColumn =table.Columns.Add("IsActive", typeof(bool));isActiveColumn.AllowDBNull = false;isActiveColumn.DefaultValue = true;table.PrimaryKey = new[] { idColumn };return table;

}

サンプルデータを追加するメソッドも用意します。

static DataTable CreateSampleData(){DataTable table = CreateEmployeeTable();

table.Rows.Add(1,"佐藤","営業部",28,320000m,new DateTime(2022, 4, 1),true);table.Rows.Add(2,"鈴木","開発部",35,450000m,new DateTime(2018, 10, 15),true);table.Rows.Add(3,"高橋","総務部",42,500000m,new DateTime(2015, 6, 1),false);table.Rows.Add(4,"田中","開発部",31,410000m,new DateTime(2020, 1, 10),true);table.Rows.Add(5,"伊藤",DBNull.Value,26,290000m,new DateTime(2024, 4, 1),true);return table;

}

3. DataTableにデータを追加する方法

3-1. Rows.Addで行を直接追加する

最も簡単な追加方法は、Rows.Addメソッドへ各列の値を渡す方法です。

DataTable table = CreateEmployeeTable();

table.Rows.Add(1,"佐藤","営業部",28,320000m,new DateTime(2022, 4, 1),true);

値は、列が定義されている順番で指定します。

列の順番は次のとおりです。

IdNameDepartmentAgeSalaryHireDateIsActive

値の数や順番を間違えると、意図しない列に値が格納されたり、型変換エラーが発生したりします。

列の値を配列として渡すこともできます。

object[] values ={2,"鈴木","開発部",35,450000m,new DateTime(2018, 10, 15),true};

table.Rows.Add(values);

3-2. NewRowでDataRowを作成して追加する

NewRowメソッドを使うと、新しいDataRowを作成してから値を設定できます。

DataTable table = CreateEmployeeTable();

DataRow row = table.NewRow();

row["Id"] = 1;row["Name"] = "佐藤";row["Department"] = "営業部";row["Age"] = 28;row["Salary"] = 320000m;row["HireDate"] = new DateTime(2022, 4, 1);row["IsActive"] = true;

table.Rows.Add(row);

NewRowを使う方法は、列名を見ながら値を設定できるため、列数が多い場合に読みやすくなります。

値を設定しただけでは、DataTableに行は追加されません。最後に必ずRows.Addを実行します。

DataRow row = table.NewRow();row["Id"] = 1;

// この時点ではDataTableに追加されていないtable.Rows.Add(row);

3-3. 複数行のデータを追加する

複数行を追加するときは、繰り返し処理を使用できます。

DataTable table = CreateEmployeeTable();

var employees = new[]{new{Id = 1,Name = "佐藤",Department = "営業部",Age = 28,Salary = 320000m,HireDate = new DateTime(2022, 4, 1),IsActive = true},new{Id = 2,Name = "鈴木",Department = "開発部",Age = 35,Salary = 450000m,HireDate = new DateTime(2018, 10, 15),IsActive = true}};

foreach (var employee in employees){table.Rows.Add(employee.Id,employee.Name,employee.Department,employee.Age,employee.Salary,employee.HireDate,employee.IsActive);}

大量の行を追加する場合は、BeginLoadDataEndLoadDataを利用すると、制約チェックなどの処理を一時的に抑えられる場合があります。

table.BeginLoadData();

try{for (int i = 1; i <= 10000; i++){table.Rows.Add(i,$"社員{i}","開発部",30,300000m,DateTime.Today,true);}}finally{table.EndLoadData();}

EndLoadDataでは制約の検証が行われるため、主キーの重複などがあると例外が発生する可能性があります。

3-4. DBNull.Valueを使ってNULLを追加する

DataTableでデータベースのNULLに相当する値を設定するときは、C#のnullではなくDBNull.Valueを使用します。

DataTable table = CreateEmployeeTable();

table.Rows.Add(1,"佐藤",DBNull.Value,28,320000m,new DateTime(2022, 4, 1),true);

NewRowを使う場合も同様です。

DataRow row = table.NewRow();

row["Id"] = 2;row["Name"] = "鈴木";row["Department"] = DBNull.Value;row["Age"] = 35;row["Salary"] = 450000m;row["HireDate"] = new DateTime(2018, 10, 15);row["IsActive"] = true;

table.Rows.Add(row);

文字列が空の場合にNULLを設定したいときは、条件演算子を利用できます。

string? department = null;

row["Department"] =string.IsNullOrWhiteSpace(department)? DBNull.Value: department;

ただし、対象列のAllowDBNullfalseの場合は、DBNull.Valueを設定できません。

3-5. 別のDataTableからデータを追加する

別のDataTableから行を追加する方法はいくつかあります。

列構成が同じ場合は、ImportRowを利用できます。

DataTable sourceTable = CreateSampleData();DataTable destinationTable = sourceTable.Clone();

foreach (DataRow sourceRow in sourceTable.Rows){destinationTable.ImportRow(sourceRow);}

Cloneは、列や制約などの構造だけをコピーします。行データはコピーされません。

DataTable structureOnly = sourceTable.Clone();

構造とデータの両方をコピーしたい場合は、Copyを使います。

DataTable copiedTable = sourceTable.Copy();

複数のDataTableを結合する場合は、Mergeも利用できます。

DataTable table1 = CreateSampleData();DataTable table2 = CreateEmployeeTable();

table2.Rows.Add(6,"山本","営業部",33,380000m,new DateTime(2019, 8, 1),true);

table1.Merge(table2);

主キーが設定されている場合、同じ主キーを持つ行は既存行と照合されます。単純に全行を追加したい場合は、主キーの重複に注意してください。

3-6. データ追加時の型変換エラーを防ぐ

DataTableの列にはデータ型が設定されているため、変換できない値を追加すると例外が発生します。

たとえば、Age列がint型であるにもかかわらず、数値に変換できない文字列を設定するとエラーになります。

DataRow row = table.NewRow();

// FormatExceptionなどの原因になるrow["Age"] = "三十歳";

外部から受け取った文字列を追加する場合は、TryParseで検証します。

string inputAge = "30";

if (int.TryParse(inputAge, out int age)){row["Age"] = age;}else{Console.WriteLine("年齢の形式が正しくありません。");}

日付も同様です。

string inputDate = "2024-04-01";

if (DateTime.TryParse(inputDate, out DateTime hireDate)){row["HireDate"] = hireDate;}else{Console.WriteLine("入社日の形式が正しくありません。");}

金額にはdecimal.TryParseを使用します。

string inputSalary = "350000";

if (decimal.TryParse(inputSalary, out decimal salary)){row["Salary"] = salary;}

列が存在するか確認してから値を設定すると、列名の間違いによる例外を防げます。

if (table.Columns.Contains("Age")){row["Age"] = 30;}

また、外部入力をDataTableへ追加するときは、次の項目を確認しましょう。

  • 必須値が入力されているか

  • 列のデータ型へ変換できるか

  • 主キーや一意制約に違反しないか

  • NULLを許可している列か

  • 文字列の長さが制限を超えていないか

4. DataTableのデータを取得・表示する方法

4-1. RowsとColumnsを指定して値を取得する

DataTableの値は、行番号と列番号を指定して取得できます。

DataTable table = CreateSampleData();

object value = table.Rows[0][1];

Console.WriteLine(value);

実行結果は次のとおりです。

佐藤

Rows[0]は1行目、[1]は2列目を表します。インデックスは0から始まります。

列番号ではなく、DataColumnを指定することもできます。

DataColumn nameColumn = table.Columns["Name"];object name = table.Rows[0][nameColumn];

Console.WriteLine(name);

値を特定の型として使用するときは、キャストまたは変換が必要です。

int id = (int)table.Rows[0]["Id"];string name = (string)table.Rows[0]["Name"];

値がDBNullの可能性がある列を直接キャストすると、例外が発生するため注意が必要です。

4-2. foreachで全行を順番に取得する

全行を順番に処理する場合は、foreachを使用します。

DataTable table = CreateSampleData();

foreach (DataRow row in table.Rows){Console.WriteLine($"{row["Id"]}: {row["Name"]}");}

実行結果の例は次のとおりです。

1: 佐藤2: 鈴木3: 高橋4: 田中5: 伊藤

すべての列を動的に取得する場合は、DataColumnを繰り返し処理します。

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

Console.WriteLine();

}

DataTableの列構成が実行時に変化する場合でも、この方法なら全項目を表示できます。

4-3. 列名を指定してデータを取得する

列名を指定すると、コードを見ただけで何の値を取得しているか分かりやすくなります。

DataRow row = table.Rows[0];

int id = Convert.ToInt32(row["Id"]);string name = Convert.ToString(row["Name"]) ?? string.Empty;int age = Convert.ToInt32(row["Age"]);

Console.WriteLine($"{id}, {name}, {age}");

列名が存在しない場合、ArgumentExceptionが発生します。動的なDataTableを扱うときは、Columns.Containsで確認できます。

if (table.Columns.Contains("Department")){Console.WriteLine(row["Department"]);}

列名の大文字と小文字の扱いは、DataTableの設定や列構成によって問題になる場合があります。コード内では、定義した列名と同じ表記を使用するのが安全です。

列名を定数化しておくと、入力ミスを減らせます。

const string IdColumn = "Id";const string NameColumn = "Name";

int id = Convert.ToInt32(row[IdColumn]);string name = Convert.ToString(row[NameColumn]) ?? string.Empty;

4-4. Fieldメソッドで型安全に値を取得する

Field<T>メソッドを使うと、列の値を指定した型で取得できます。

DataRow row = table.Rows[0];

int id = row.Field<int>("Id");string? name = row.Field<string>("Name");decimal salary = row.Field<decimal>("Salary");DateTime hireDate = row.Field<DateTime>("HireDate");

Field<T>はLINQでDataTableを操作するときにもよく使用します。

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

NULLを許可する値型の列では、Nullable型を指定します。

int? age = row.Field<int?>("Age");DateTime? date = row.Field<DateTime?>("HireDate");

DBNullが格納された列を、NULL非許容の型で取得すると例外になります。

// DepartmentがDBNullの場合でもstring?ならnullとして取得できるstring? department = row.Field<string?>("Department");

Field<T>は値を別の型へ自動変換するメソッドではありません。たとえば、列の型がintである場合にField<string>で取得することはできません。

int age = row.Field<int>("Age");string ageText = age.ToString();

4-5. NULLを判定して安全に値を取得する

DataTableのNULLはDBNull.Valueで表現されます。NULLを判定するには、IsNullメソッドが便利です。

DataRow row = table.Rows[4];

if (row.IsNull("Department")){Console.WriteLine("部署は未設定です。");}else{Console.WriteLine(row["Department"]);}

DBNull.Valueと比較する方法もあります。

if (row["Department"] == DBNull.Value){Console.WriteLine("部署は未設定です。");}

Field<T>を使用する場合は、NULL許容型として取得できます。

string? department = row.Field<string?>("Department");

Console.WriteLine(department ?? "未設定");

値型の場合はNullable型を使います。

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

if (age.HasValue){Console.WriteLine(age.Value);}else{Console.WriteLine("年齢は未設定です。");}

Convert.ToStringを使うと、DBNull.Valueは空文字列になります。

string department =Convert.ToString(row["Department"]) ?? string.Empty;

ただし、この方法では「NULL」と「もともとの空文字列」を区別できません。両者を区別する必要がある場合は、先にIsNullで確認しましょう。

4-6. 行数と列数を取得する

DataTableの行数はRows.Count、列数はColumns.Countで取得できます。

DataTable table = CreateSampleData();

int rowCount = table.Rows.Count;int columnCount = table.Columns.Count;

Console.WriteLine($"行数: {rowCount}");Console.WriteLine($"列数: {columnCount}");

実行結果は次のとおりです。

行数: 5列数: 7

データが存在するか確認する場合は、Rows.Countが0より大きいか判定します。

if (table.Rows.Count > 0){Console.WriteLine("データがあります。");}else{Console.WriteLine("データがありません。");}

列が存在するか確認するときは、Columns.Containsを使用できます。

if (table.Columns.Contains("Name")){Console.WriteLine("Name列があります。");}

5. DataTableのデータを検索する方法

5-1. Selectメソッドで条件に一致する行を検索する

DataTableのSelectメソッドを使うと、条件に一致するDataRowの配列を取得できます。

DataTable table = CreateSampleData();

DataRow[] rows = table.Select("Department = '開発部'");

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

実行結果は次のとおりです。

鈴木田中

Selectの条件式では、列名、比較演算子、値を文字列で指定します。

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

文字列はシングルクォートで囲みます。

DataRow[] rows = table.Select("Name = '佐藤'");

条件式の列名をユーザー入力から直接組み立てると、不正な式や特殊文字によってエラーになる可能性があります。検索条件に外部入力を使用する場合は、値を適切にエスケープしてください。

5-2. 完全一致・部分一致・範囲指定で検索する

完全一致検索では、=を使用します。

DataRow[] rows = table.Select("Department = '営業部'");

不一致検索では、<>を使用します。

DataRow[] rows = table.Select("Department <> '開発部'");

数値の範囲を検索する場合は、比較演算子を組み合わせます。

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

INを使うと、複数の候補から一致する値を検索できます。

DataRow[] rows =table.Select("Department IN ('営業部', '開発部')");

文字列の部分一致にはLIKEを使用します。

DataRow[] rows = table.Select("Name LIKE '佐%'");

検索条件が固定されている場合はSelectが手軽です。一方、複雑な条件やC#のメソッドを使った判定が必要な場合はLINQが適しています。

5-3. AND・ORを使って複数条件で検索する

複数の条件をすべて満たす行を検索するときは、ANDを使用します。

DataRow[] rows =table.Select("Department = '開発部' AND Age >= 30");

いずれかの条件を満たす行を検索するときは、ORを使用します。

DataRow[] rows =table.Select("Department = '営業部' OR Department = '総務部'");

ANDとORを組み合わせる場合は、括弧を使用して評価順序を明確にします。

DataRow[] rows = table.Select("(Department = '営業部' OR Department = '開発部')" +" AND Age >= 30");

否定条件にはNOTを使用できます。

DataRow[] rows =table.Select("NOT Department = '開発部'");

ただし、NULLを含む列では通常の比較だけでは対象にならない場合があります。NULLも含めて条件を作る場合は、IS NULLを明示します。

DataRow[] rows = table.Select("Department <> '開発部' OR Department IS NULL");

5-4. LIKEを使って文字列を部分検索する

文字列を部分検索するときは、LIKEを使用します。

前方一致の例です。

DataRow[] rows = table.Select("Name LIKE '佐%'");

後方一致の例です。

DataRow[] rows = table.Select("Name LIKE '%木'");

文字列を含む行を検索する例です。

DataRow[] rows = table.Select("Name LIKE '%藤%'");

検索文字列にシングルクォートが含まれる場合は、シングルクォートを2つに置換します。

string keyword = "O'Neil";string escapedKeyword = keyword.Replace("'", "''");

DataRow[] rows =table.Select($"Name LIKE '%{escapedKeyword}%'");

角括弧などの特殊文字もLIKEのパターンとして解釈される可能性があります。複雑な外部入力を検索する場合は、LINQでContainsを使用する方法も分かりやすいでしょう。

string keyword = "藤";

var rows = table.AsEnumerable().Where(row =>(row.Field<string>("Name") ?? string.Empty).Contains(keyword));

大文字と小文字の区別は、DataTableのCaseSensitiveプロパティに影響されます。

table.CaseSensitive = true;

5-5. 日付や数値を条件にして検索する

数値はクォートで囲まずに指定します。

DataRow[] rows = table.Select("Salary >= 400000");

範囲検索も可能です。

DataRow[] rows =table.Select("Salary >= 300000 AND Salary < 450000");

日付条件は、文字列を直接組み立てるより、LINQでDateTimeとして比較する方法が安全です。

DateTime targetDate = new DateTime(2020, 1, 1);

var rows = table.AsEnumerable().Where(row =>row.Field<DateTime>("HireDate") >= targetDate);

終了日を含む範囲検索の例です。

DateTime startDate = new DateTime(2018, 1, 1);DateTime endDate = new DateTime(2022, 12, 31);

var rows = table.AsEnumerable().Where(row =>{DateTime hireDate = row.Field<DateTime>("HireDate");

    return hireDate &gt;= startDate &amp;&amp;hireDate &lt;= endDate;});</span></code></pre><p><span>時刻を含むDateTimeを日単位で検索する場合は、翌日の直前までを指定するより、「開始日時以上、翌日未満」で判定すると確実です。</span></p><pre dir="ltr"><code dir="ltr"><span>DateTime targetDay = new DateTime(2022, 4, 1);

DateTime nextDay = targetDay.AddDays(1);

var rows = table.AsEnumerable().Where(row =>{DateTime hireDate = row.Field<DateTime>("HireDate");

    return hireDate &gt;= targetDay &amp;&amp;hireDate &lt; nextDay;});</span></code></pre><h3><span>5-6. IS NULLでNULLを含む行を検索する</span></h3><p><span>NULLを含む行を検索する場合は、</span><code dir="ltr"><span>IS NULL</span></code><span>を使用します。</span></p><pre dir="ltr"><code dir="ltr"><span>DataRow[] rows =table.Select("Department IS NULL");</span></code></pre><p><span>NULLではない行を検索する場合は、</span><code dir="ltr"><span>IS NOT NULL</span></code><span>を使用します。</span></p><pre dir="ltr"><code dir="ltr"><span>DataRow[] rows =table.Select("Department IS NOT NULL");</span></code></pre><p><span>LINQでNULLを検索する場合は、</span><code dir="ltr"><span>Field&lt;T&gt;</span></code><span>でnullとして取得できます。</span></p><pre dir="ltr"><code dir="ltr"><span>var rows = table.AsEnumerable().Where(row =&gt;row.Field&lt;string?&gt;("Department") == null);</span></code></pre><p><code dir="ltr"><span>IsNull</span></code><span>メソッドを使う方法もあります。</span></p><pre dir="ltr"><code dir="ltr"><span>var rows = table.AsEnumerable().Where(row =&gt; row.IsNull("Department"));</span></code></pre><p><span>NULLと空文字列は異なる値です。NULLと空文字列の両方を未設定として扱う場合は、条件を組み合わせます。</span></p><pre dir="ltr"><code dir="ltr"><span>DataRow[] rows = table.Select("Department IS NULL OR Department = ''");</span></code></pre><p><span>LINQでは次のように記述できます。</span></p><pre dir="ltr"><code dir="ltr"><span>var rows = table.AsEnumerable().Where(row =&gt;string.IsNullOrWhiteSpace(row.Field&lt;string?&gt;("Department")));</span></code></pre><h3><span>5-7. Rows.Findで主キーから1行を検索する</span></h3><p><span>主キーが設定されているDataTableでは、</span><code dir="ltr"><span>Rows.Find</span></code><span>を使って1行を検索できます。</span></p><pre dir="ltr"><code dir="ltr"><span>DataTable table = CreateSampleData();

DataRow? row = table.Rows.Find(2);

if (row != null){Console.WriteLine(row["Name"]);}else{Console.WriteLine("対象データがありません。");}

実行結果は次のとおりです。

鈴木

Rows.Findを利用するには、あらかじめ主キーを設定しておく必要があります。

table.PrimaryKey = new[]{table.Columns["Id"]};

主キーが設定されていない状態でRows.Findを実行すると、例外が発生します。

複合主キーの場合は、キー値をobject配列で指定します。

DataRow? row =table.Rows.Find(new object[] { 10, 1001 });

主キーが分かっている場合は、条件式を解析するSelectよりRows.Findのほうが、検索の意図が明確です。

5-8. LINQでDataTableを検索する

AsEnumerableを使うと、DataTableに対してLINQを使用できます。

DataTable table = CreateSampleData();

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

結果を表示します。

foreach (DataRow row in rows){Console.WriteLine($"{row.Field<string>("Name")} " +$"{row.Field<int>("Age")}歳");}

複数条件もC#の論理演算子で記述できます。

var rows = table.AsEnumerable().Where(row =>row.Field<string?>("Department") == "開発部" &&row.Field<int>("Age") >= 30);

部分一致検索では、Containsを利用できます。

string keyword = "藤";

var rows = table.AsEnumerable().Where(row =>(row.Field<string>("Name") ?? string.Empty).Contains(keyword));

大文字と小文字を区別せずに検索する場合は、StringComparisonを指定します。

string keyword = "sato";

var rows = table.AsEnumerable().Where(row =>(row.Field<string>("Name") ?? string.Empty).Contains(keyword,StringComparison.OrdinalIgnoreCase));

LINQは型を利用して条件を記述できるため、文字列条件式の記述ミスを減らしやすい方法です。

5-9. 検索結果を新しいDataTableに変換する

LINQの検索結果を新しいDataTableへ変換するには、CopyToDataTableを使用します。

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

DataTable resultTable = rows.CopyToDataTable();

Selectの検索結果も、LINQへ変換してからCopyToDataTableを使用できます。

DataRow[] rows =table.Select("Department = '開発部'");

DataTable resultTable =rows.CopyToDataTable();

元のDataTableと同じ構造を持つ空のDataTableを作成し、検索結果をImportRowで追加する方法もあります。

DataTable resultTable = table.Clone();

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

必要な列だけを持つ新しいDataTableを作る場合は、明示的に列を定義します。

DataTable resultTable = new DataTable();

resultTable.Columns.Add("Name", typeof(string));resultTable.Columns.Add("Salary", typeof(decimal));

foreach (DataRow row in rows){resultTable.Rows.Add(row.Field<string>("Name"),row.Field<decimal>("Salary"));}

5-10. 検索結果が0件の場合のCopyToDataTableエラーを防ぐ

CopyToDataTableは、検索結果が0件の場合にInvalidOperationExceptionを発生させます。

次のコードは、該当する行がないとエラーになります。

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

// 0件の場合は例外が発生するDataTable resultTable = rows.CopyToDataTable();

0件の場合を考慮するには、Anyで確認します。

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

DataTable resultTable;

if (rows.Any()){resultTable = rows.CopyToDataTable();}else{resultTable = table.Clone();}

ただし、列挙処理を複数回実行したくない場合は、先にListへ変換できます。

List<DataRow> rows = table.AsEnumerable().Where(row =>row.Field<int>("Age") >= 100).ToList();

DataTable resultTable = rows.Count > 0? rows.CopyToDataTable(): table.Clone();

Selectの結果を使う場合は、配列の長さを確認します。

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

DataTable resultTable = rows.Length > 0? rows.CopyToDataTable(): table.Clone();

検索結果が0件でも、元のDataTableと同じ列構成を持つ空のDataTableを返すようにすると、呼び出し側の処理を統一できます。

6. DataTableのデータを並べ替え・絞り込みする方法

6-1. Selectメソッドで検索結果を並べ替える

Selectメソッドの第2引数には、並べ替え条件を指定できます。

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

ASCは昇順、DESCは降順です。

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

複数列で並べ替えることもできます。

DataRow[] rows =table.Select(string.Empty,"Department ASC, Age DESC");

条件を指定せず、すべての行を並べ替える場合は、空文字列を指定します。

DataRow[] rows =table.Select("","HireDate ASC");

結果はDataRow配列であり、元のDataTableの行順は変更されません。

6-2. DataViewのRowFilterでデータを絞り込む

DataViewのRowFilterを使うと、DataTableの表示対象を絞り込めます。

DataView view = new DataView(table);

view.RowFilter ="Department = '開発部'";

絞り込んだ結果を取得します。

foreach (DataRowView rowView in view){Console.WriteLine(rowView["Name"]);}

DataTableのDefaultViewを使用することもできます。

DataView view = table.DefaultView;view.RowFilter = "Age >= 30";

フィルターを解除するときは、空文字列を設定します。

view.RowFilter = string.Empty;

DataViewの内容をDataTableへ変換するには、ToTableを使用します。

DataTable filteredTable = view.ToTable();

RowFilterは元のDataTableから行を削除する処理ではありません。DataView上で表示される行だけが変化します。

6-3. DataViewのSortで昇順・降順に並べ替える

DataViewのSortプロパティを使うと、表示順を変更できます。

年齢の昇順で並べ替える例です。

DataView view = new DataView(table);

view.Sort = "Age ASC";

給与の降順で並べ替える例です。

view.Sort = "Salary DESC";

複数列を指定することもできます。

view.Sort ="Department ASC, Salary DESC";

絞り込みと並べ替えを同時に使用できます。

DataView view = new DataView(table);

view.RowFilter ="Department = '開発部'";

view.Sort ="Salary DESC";

結果を新しいDataTableへ変換します。

DataTable resultTable = view.ToTable();

DataViewは、画面上の一覧にフィルター機能や並べ替え機能を実装するときに便利です。

6-4. LINQのOrderByで並べ替える

LINQでは、OrderByで昇順、OrderByDescendingで降順に並べ替えます。

var rows = table.AsEnumerable().OrderBy(row =>row.Field<int>("Age"));

降順にする場合は次のように記述します。

var rows = table.AsEnumerable().OrderByDescending(row =>row.Field<decimal>("Salary"));

複数列で並べ替える場合は、ThenByまたはThenByDescendingを使用します。

var rows = table.AsEnumerable().OrderBy(row =>row.Field<string?>("Department")).ThenByDescending(row =>row.Field<decimal>("Salary"));

検索と並べ替えを組み合わせることもできます。

var rows = table.AsEnumerable().Where(row =>row.Field<bool>("IsActive")).OrderByDescending(row =>row.Field<decimal>("Salary"));

新しいDataTableへ変換する場合は、0件を考慮します。

List<DataRow> rows = table.AsEnumerable().OrderBy(row =>row.Field<int>("Age")).ToList();

DataTable sortedTable = rows.Count > 0? rows.CopyToDataTable(): table.Clone();

6-5. 重複しない値を抽出する

特定の列から重複しない値を抽出する場合は、LINQのDistinctを使用します。

List<string> departments = table.AsEnumerable().Select(row =>row.Field<string?>("Department")).Where(department =>!string.IsNullOrEmpty(department)).Select(department => department!).Distinct().ToList();

結果を表示します。

foreach (string department in departments){Console.WriteLine(department);}

複数列の組み合わせを重複なしで取得する場合は、匿名型へ変換してDistinctを使用できます。

var values = table.AsEnumerable().Select(row => new{Department =row.Field<string?>("Department"),IsActive =row.Field<bool>("IsActive")}).Distinct().ToList();

DataViewのToTableでも重複を除外できます。

DataView view = new DataView(table);

DataTable distinctTable = view.ToTable(true,"Department");

第1引数のtrueは重複を除外する指定です。それ以降の引数には、抽出する列名を指定します。

複数列を指定する場合は次のように記述します。

DataTable distinctTable = view.ToTable(true,"Department","IsActive");

6-6. 条件に一致する件数や合計値を集計する

条件に一致する件数は、LINQのCountで取得できます。

int count = table.AsEnumerable().Count(row =>row.Field<string?>("Department") == "開発部");

Console.WriteLine($"開発部の人数: {count}");

給与の合計はSumで取得できます。

decimal totalSalary = table.AsEnumerable().Sum(row =>row.Field<decimal>("Salary"));

Console.WriteLine($"給与合計: {totalSalary}");

条件に一致する行だけを集計できます。

decimal developmentTotal = table.AsEnumerable().Where(row =>row.Field<string?>("Department") == "開発部").Sum(row =>row.Field<decimal>("Salary"));

平均値はAverageで取得します。

double averageAge = table.AsEnumerable().Average(row =>row.Field<int>("Age"));

最大値と最小値は、MaxMinを使用します。

decimal maxSalary = table.AsEnumerable().Max(row =>row.Field<decimal>("Salary"));

decimal minSalary = table.AsEnumerable().Min(row =>row.Field<decimal>("Salary"));

部署ごとに集計する場合は、GroupByを使用します。

var summaries = table.AsEnumerable().GroupBy(row =>row.Field<string?>("Department") ?? "未設定").Select(group => new{Department = group.Key,Count = group.Count(),TotalSalary = group.Sum(row =>row.Field<decimal>("Salary")),AverageAge = group.Average(row =>row.Field<int>("Age"))});
foreach (var summary in summaries){Console.WriteLine($"{summary.Department}: " +$"{summary.Count}人、" +$"給与合計{summary.TotalSalary}円、" +$"平均年齢{summary.AverageAge:F1}歳");}

DataTableのComputeメソッドを使う方法もあります。

object result = table.Compute("SUM(Salary)","Department = '開発部'");

decimal total = result == DBNull.Value? 0m: Convert.ToDecimal(result);

件数を求める例です。

object result = table.Compute("COUNT(Id)","IsActive = true");

int count = Convert.ToInt32(result);

Computeは簡潔ですが、集計式や条件を文字列で指定します。複雑な集計では、型を利用できるLINQのほうが保守しやすい場合があります。

7. DataTableのデータを更新する方法

7-1. 行番号を指定して値を更新する

行番号を指定して更新する場合は、RowsからDataRowを取得して値を代入します。

DataTable table = CreateSampleData();

table.Rows[0]["Name"] = "佐藤太郎";table.Rows[0]["Age"] = 29;table.Rows[0]["Salary"] = 340000m;

DataRowを変数へ格納すると、複数列の更新が読みやすくなります。

DataRow row = table.Rows[0];

row["Name"] = "佐藤太郎";row["Department"] = "企画部";row["Age"] = 29;

行番号が範囲外の場合は例外が発生するため、事前に行数を確認します。

int rowIndex = 0;

if (rowIndex >= 0 &&rowIndex < table.Rows.Count){table.Rows[rowIndex]["Name"] ="佐藤太郎";}

行番号はデータの追加や削除、並べ替えによって変化します。特定のデータを更新する場合は、主キーや検索条件を使う方法が安全です。

7-2. 検索条件に一致した行を更新する

Selectで条件に一致する行を検索し、foreachで更新できます。

DataRow[] rows =table.Select("Department = '開発部'");

foreach (DataRow row in rows){row["Salary"] =row.Field<decimal>("Salary") + 20000m;}

複数項目を更新することもできます。

DataRow[] rows =table.Select("IsActive = false");

foreach (DataRow row in rows){row["Department"] = DBNull.Value;row["Salary"] = 0m;}

LINQで検索した行も、そのまま更新できます。

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

foreach (DataRow row in rows){row["IsActive"] = false;}

更新前後の値を明確に管理したい場合は、BeginEditEndEditを利用できます。

DataRow row = table.Rows[0];

row.BeginEdit();

try{row["Name"] = "佐藤太郎";row["Salary"] = 350000m;

row.EndEdit();

}catch{row.CancelEdit();throw;}

DataTableは行の状態をRowStateで管理します。

foreach (DataRow row in table.Rows){Console.WriteLine(row.RowState);}

主な状態は次のとおりです。

RowState意味
Added新しく追加された行
Modified値が変更された行
Deleted削除された行
Unchanged変更されていない行
DetachedDataTableに所属していない行

AcceptChangesを実行すると、現在の内容が確定され、行の状態は原則としてUnchangedになります。

table.AcceptChanges();

データベースへ変更を反映する処理でDataAdapterを使用する場合、更新前にAcceptChangesを呼ぶと変更状態を判別できなくなることがあります。データベース更新との組み合わせでは、呼び出すタイミングに注意しましょう。

7-3. 主キーで検索した行を更新する

主キーが設定されている場合は、Rows.Findで対象行を検索して更新できます。

DataTable table = CreateSampleData();

DataRow? row = table.Rows.Find(2);

if (row != null){row["Name"] = "鈴木一郎";row["Salary"] = 470000m;}else{Console.WriteLine("更新対象の行が見つかりません。");}

更新処理をメソッド化する例です。

static bool UpdateEmployee(DataTable table,int id,string name,string? department,int age,decimal salary){DataRow? row = table.Rows.Find(id);

if (row == null){return false;}row<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["Name"]</span> = name;row<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["Department"]</span> =string.IsNullOrWhiteSpace(department)? DBNull.Value: department;row<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["Age"]</span> = age;row<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["Salary"]</span> = salary;return true;

}

呼び出し例は次のとおりです。

bool updated = UpdateEmployee(table,2,"鈴木一郎","開発部",36,480000m);

Console.WriteLine(updated? "更新しました。": "対象データがありません。");

DataTableから行を削除する場合は、DataRowのDeleteメソッドを使用できます。

DataRow? row = table.Rows.Find(3);

if (row != null){row.Delete();}

Deleteを実行すると、行は削除状態になります。DataAdapterによるデータベース更新では、この削除状態を利用してDELETE処理が行われます。

メモリ上のDataTableから行を直ちに取り除きたい場合は、Rows.Removeも使用できます。

DataRow? row = table.Rows.Find(3);

if (row != null){table.Rows.Remove(row);}

行番号を指定して削除する場合は、RemoveAtを使用します。

if (table.Rows.Count > 0){table.Rows.RemoveAt(0);}

複数行を削除する場合、foreachでDataRowCollectionを直接列挙しながらRemoveするとコレクションが変更されるため、例外の原因になります。先に対象行を配列へ取得してから削除します。

DataRow[] rows =table.Select("IsActive = false");

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

全行を削除するだけで変更履歴を必要としない場合は、Clearを使用できます。

table.Clear();

DeleteRemoveClearは用途が異なります。データベースへの削除反映が必要な場合は行の削除状態を残すDelete、DataTableから即座に取り除く場合はRemove、全データを空にする場合はClearを使い分けましょう。

まとめ

C#のDataTableは、表形式のデータをメモリ上で柔軟に管理できるクラスです。列の型や主キー、NULL制約を設定することで、データベースのテーブルに近い構造を作成できます。

DataTableを扱う際の基本的なポイントは次のとおりです。

  • Columns.Addで列名とデータ型を定義する

  • Rows.AddまたはNewRowで行を追加する

  • NULLはnullではなくDBNull.Valueで表現する

  • 値の取得には列名やField<T>を使用する

  • SelectやLINQで条件に一致する行を検索する

  • 主キー検索にはRows.Findを使用する

  • DataViewで絞り込みや並べ替えを行う

  • SumCountGroupByなどで集計する

  • 検索したDataRowへ値を代入して更新する

  • DeleteRemoveClearを目的に応じて使い分ける

手軽な条件検索にはSelect、型安全で複雑な処理にはLINQ、画面表示用の絞り込みや並べ替えにはDataViewが適しています。

一方、DataTableは列の値をobjectとして扱う場面が多く、型の不一致やDBNullによる例外が発生しやすい点に注意が必要です。列のデータ型を明確に定義し、Field<T>TryParseIsNullなどを活用することで、安全で読みやすいコードを作成できます。