C# DataTableの使い方完全ガイド|作成・検索・並び替え・LINQ・DataGridView表示までサンプルで解説

はじめに

C#で表形式のデータを扱うときによく使われるクラスがDataTableです。DataTableは、行と列で構成されたメモリ上のテーブルを表現できるクラスで、データベースの検索結果、CSVやExcelから読み込んだデータ、画面表示用の一覧データなどを扱う場面で広く利用されています。

特にWindows Formsアプリケーションでは、DataGridViewにそのままバインドできるため、一覧表示・検索・並び替え・編集といった処理を比較的簡単に実装できます。

この記事では、C#のDataTableについて、作成方法、データの追加・取得・更新・削除、検索、並び替え、LINQによる操作、DataGridViewへの表示、データベース連携、よくあるエラーまで、サンプルコード付きで解説します。

1. C#のDataTableとは?基本概念と使いどころ

DataTableは、System.Data名前空間に含まれるクラスで、メモリ上に表形式のデータを保持するために使用します。

イメージとしては、Excelのシートやデータベースのテーブルに近い構造です。

DataTable
├─ Columns:列情報
│ ├─ Id
│ ├─ Name
│ └─ Age
└─ Rows:行データ
├─ 1, "田中", 25
├─ 2, "佐藤", 30
└─ 3, "鈴木", 28

C#でDataTableを使うと、データベースから取得した結果を一時的に保持したり、画面に一覧表示したり、条件で絞り込んだり、並び替えたりできます。

1-1. DataTableでできること

DataTableでは、主に次のような操作ができます。

C#
DataTable table = new DataTable();

DataTableを使うと、以下のような処理を実装できます。

  • 列を定義する

  • 行を追加する

  • 行や列の値を取得する

  • 値を更新する

  • 行を削除する

  • 条件に一致するデータを検索する

  • データを並び替える

  • 合計や平均を集計する

  • LINQで絞り込みやグループ化を行う

  • DataGridViewに表示する

  • データベースの検索結果を格納する

たとえば、社員一覧、商品一覧、売上一覧、顧客一覧のような表形式のデータを扱う場合に便利です。

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

DataTableを理解するには、関連するクラスとの違いを押さえることが重要です。

クラス役割
DataTable1つの表を表す
DataSet複数のDataTableをまとめて管理する
DataRowDataTable内の1行を表す
DataColumnDataTable内の1列を表す
DataViewDataTableの表示用ビュー。検索や並び替えに使う

関係性を簡単に表すと、次のようになります。

DataSet
├─ DataTable
│ ├─ DataColumn
│ ├─ DataColumn
│ ├─ DataRow
│ └─ DataRow
└─ DataTable
├─ DataColumn
└─ DataRow

DataTableは1つのテーブルです。複数のテーブルをまとめたい場合はDataSetを使います。

DataRowは行データ、DataColumnは列定義です。DataViewDataTableのデータを並び替えたり、絞り込んだりして表示するためのビューです。

1-3. DataTableがよく使われる場面

C#のDataTableは、特に次のような場面で使われます。

C#
// データベースから取得した結果をDataTableに格納する例
DataTable table = new DataTable();

代表的な利用シーンは以下です。

  • SQLの検索結果を格納する

  • Windows FormsのDataGridViewに一覧表示する

  • CSVやExcelから読み込んだデータを一時的に保持する

  • データを条件検索・並び替えして表示する

  • 帳票出力やファイル出力の前処理として使う

  • 複数のデータソースを結合・加工する

業務アプリケーションでは、データベースと画面表示の間にDataTableを挟む構成がよく使われます。

1-4. DataTableを使うメリット・デメリット

DataTableには便利な点が多い一方で、注意点もあります。

メリットは次のとおりです。

  • 表形式のデータを扱いやすい

  • 列名でデータにアクセスできる

  • DataGridViewにそのまま表示できる

  • SelectComputeで検索・集計ができる

  • データベース連携と相性がよい

  • スキーマ情報を持てる

  • 主キーや一意制約を設定できる

一方、デメリットもあります。

  • List<T>に比べると型安全性が低い

  • 列名を文字列で指定するためタイプミスに弱い

  • 大量データではメモリ使用量が増えやすい

  • 複雑なロジックでは可読性が下がりやすい

  • ドメインモデルとしては扱いにくい

たとえば、次のようなコードは列名を文字列で指定するため、コンパイル時には間違いに気づけません。

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

もし"Name""Nmae"のように間違えても、コンパイルエラーにはならず、実行時エラーになります。

1-5. List<T>や配列ではなくDataTableを使うべきケース

C#では、表形式のデータを扱う方法としてList<T>や配列もあります。

C#
List<Person> people = new List<Person>();

では、どのような場合にDataTableを選ぶべきなのでしょうか。

DataTableが向いているケースは以下です。

  • データベースの検索結果をそのまま扱いたい

  • 列構成が実行時に変わる

  • DataGridViewに簡単に表示したい

  • CSVやExcelのような表データを汎用的に扱いたい

  • 列名ベースで柔軟に処理したい

  • DataAdapterと連携したい

一方、List<T>が向いているケースは以下です。

  • データ構造が固定されている

  • 型安全に扱いたい

  • ビジネスロジックを明確にしたい

  • LINQを中心に処理したい

  • Web APIやJSON変換と相性よく使いたい

たとえば、社員データの構造が明確に決まっているなら、次のようなクラスを作ってList<Employee>で扱う方が保守しやすいです。

C#
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = "";
public int Age { get; set; }
}

一方、データベースやCSVから読み込んだ任意の列をそのまま扱いたい場合は、DataTableが便利です。

2. DataTableを使うための準備

C#でDataTableを使うには、必要な名前空間を追加します。

2-1. 必要な名前空間

基本的には次の名前空間を使用します。

C#
using System;
using System.Data;

LINQでDataTableを操作する場合は、次の名前空間も使用します。

C#
using System.Linq;

AsEnumerable()Field<T>()を使う場合は、環境によってSystem.Data.DataSetExtensionsが必要になることがあります。

.NET Frameworkでは参照設定にSystem.Data.DataSetExtensionsを追加する必要がある場合があります。

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

Windows FormsでDataGridViewに表示する場合は、フォーム側で次のような名前空間も使用します。

C#
using System.Windows.Forms;

2-2. コンソールアプリ・Windows Formsでの基本構成

コンソールアプリでは、Mainメソッド内でDataTableを作成して動作確認できます。

C#
using System;
using System.Data;

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

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);

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

Windows Formsでは、作成したDataTableDataGridViewDataSourceに設定します。

C#
dataGridView1.DataSource = table;

これだけで、DataTableの内容を一覧表示できます。

2-3. サンプルで使用するDataTableの完成イメージ

この記事では、主に社員情報を例にして解説します。

完成イメージは次のようなDataTableです。

IdNameDepartmentAgeSalaryJoinDate
1田中営業253000002022/04/01
2佐藤開発304200002020/10/01
3鈴木開発283900002021/07/15
4高橋総務354500002019/01/10

このテーブルを作成するメソッドを用意しておくと、検索・並び替え・LINQのサンプルで使い回せます。

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

table.Columns.Add("Id", typeof(int));
table.Columns.Add("Name", typeof(string));
table.Columns.Add("Department", typeof(string));
table.Columns.Add("Age", typeof(int));
table.Columns.Add("Salary", typeof(decimal));
table.Columns.Add("JoinDate", typeof(DateTime));

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

table.Rows.Add(1, "田中", "営業", 25, 300000m, new DateTime(2022, 4, 1));
table.Rows.Add(2, "佐藤", "開発", 30, 420000m, new DateTime(2020, 10, 1));
table.Rows.Add(3, "鈴木", "開発", 28, 390000m, new DateTime(2021, 7, 15));
table.Rows.Add(4, "高橋", "総務", 35, 450000m, new DateTime(2019, 1, 10));

return table;
}

2-4. よく使う型と列定義の考え方

DataColumnを追加するときは、列名とデータ型を指定できます。

C#
table.Columns.Add("Name", typeof(string));
table.Columns.Add("Age", typeof(int));
table.Columns.Add("Salary", typeof(decimal));
table.Columns.Add("JoinDate", typeof(DateTime));
table.Columns.Add("IsActive", typeof(bool));

よく使う型は次のとおりです。

C#の型用途
string名前、住所、コードなど
intID、年齢、数量など
decimal金額、単価など
double小数を含む計算値など
DateTime日付、日時など
bool有効・無効、チェック状態など

金額を扱う場合は、誤差を避けるためdoubleよりdecimalを使うのが一般的です。

C#
table.Columns.Add("Price", typeof(decimal));

列定義では、後から値を取得しやすいように、列名をわかりやすく統一することが大切です。

C#
table.Columns.Add("EmployeeId", typeof(int));
table.Columns.Add("EmployeeName", typeof(string));

3. DataTableの作成方法

ここからは、C#でDataTableを作成する基本的な方法を解説します。

3-1. 空のDataTableを作成する

空のDataTableは、次のように作成します。

C#
DataTable table = new DataTable();

テーブル名を指定することもできます。

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

テーブル名は必須ではありませんが、複数のDataTableを扱う場合やDataSetに追加する場合は、設定しておくと管理しやすくなります。

C#
Console.WriteLine(table.TableName);

3-2. DataColumnで列を追加する

DataTableに列を追加するには、Columns.Addを使います。

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

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

この書き方では、列のデータ型は既定でstringになります。

明確に型を指定したい場合は、DataColumnを使います。

C#
DataColumn idColumn = new DataColumn("Id", typeof(int));
DataColumn nameColumn = new DataColumn("Name", typeof(string));
DataColumn ageColumn = new DataColumn("Age", typeof(int));

table.Columns.Add(idColumn);
table.Columns.Add(nameColumn);
table.Columns.Add(ageColumn);

3-3. 列名とデータ型を指定して作成する

実務では、列名とデータ型を同時に指定する書き方がよく使われます。

C#
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("Salary", typeof(decimal));
table.Columns.Add("JoinDate", typeof(DateTime));

このように型を指定しておくと、値を追加するときに不正な型が入るのを防ぎやすくなります。

C#
table.Rows.Add(1, "田中", 25, 300000m, new DateTime(2022, 4, 1));

もしAge列に文字列を入れようとすると、型変換エラーになる可能性があります。

C#
// Age列はint型なので不正
// table.Rows.Add(1, "田中", "二十五", 300000m, DateTime.Today);

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

DataTableには主キーを設定できます。主キーを設定すると、Rows.Findで高速に検索できます。

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

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

table.PrimaryKey = new DataColumn[] { idColumn };

主キーを設定した状態で行を追加します。

C#
table.Rows.Add(1, "田中", 25);
table.Rows.Add(2, "佐藤", 30);

主キーで検索する場合は、次のように書きます。

C#
DataRow row = table.Rows.Find(1);

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

主キー列には重複値を入れられません。

C#
// 主キーが重複するためエラー
// table.Rows.Add(1, "鈴木", 28);

3-5. AutoIncrement列を作成する

ID列のように、自動採番したい列にはAutoIncrementを設定します。

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

DataColumn idColumn = new DataColumn("Id", typeof(int));
idColumn.AutoIncrement = true;
idColumn.AutoIncrementSeed = 1;
idColumn.AutoIncrementStep = 1;

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

table.PrimaryKey = new DataColumn[] { idColumn };

行を追加するときは、ID列に値を指定しなくても自動で採番されます。

C#
table.Rows.Add(null, "田中", 25);
table.Rows.Add(null, "佐藤", 30);

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

ただし、Rows.Addで列数分の値を渡す必要がある場合は、ID列にnullを渡すか、NewRowで行を作成してから追加すると扱いやすいです。

C#
DataRow row = table.NewRow();
row["Name"] = "鈴木";
row["Age"] = 28;
table.Rows.Add(row);

3-6. AllowDBNull・DefaultValue・Uniqueを設定する

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

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

DataColumn idColumn = new DataColumn("Id", typeof(int));
idColumn.Unique = true;
idColumn.AllowDBNull = false;

DataColumn nameColumn = new DataColumn("Name", typeof(string));
nameColumn.AllowDBNull = false;

DataColumn departmentColumn = new DataColumn("Department", typeof(string));
departmentColumn.DefaultValue = "未所属";

table.Columns.Add(idColumn);
table.Columns.Add(nameColumn);
table.Columns.Add(departmentColumn);

table.PrimaryKey = new DataColumn[] { idColumn };

AllowDBNull = falseにすると、NULL値を許可しません。

C#
// NameがNULLなのでエラーになる可能性がある
// table.Rows.Add(1, DBNull.Value, "営業");

DefaultValueを設定すると、値を指定しなかった場合に既定値が入ります。

C#
DataRow row = table.NewRow();
row["Id"] = 1;
row["Name"] = "田中";
table.Rows.Add(row);

Console.WriteLine(row["Department"]); // 未所属

Unique = trueを設定すると、その列に重複した値を入れられません。

C#
// Id列がUniqueなので重複不可
// table.Rows.Add(1, "佐藤", "開発");

4. DataTableにデータを追加・取得・更新・削除する

DataTableの基本操作として、行の追加、値の取得、更新、削除を見ていきます。

4-1. DataRowを使って行を追加する

DataRowを使うと、列名を指定して値を設定できます。

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

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

DataRow row = table.NewRow();
row["Id"] = 1;
row["Name"] = "田中";
row["Age"] = 25;

table.Rows.Add(row);

NewRow()DataTableの構造に合った空の行を作成し、値を設定してからRows.Addで追加します。

列数が多い場合や、一部の列だけ値を設定したい場合に便利です。

C#
DataRow row2 = table.NewRow();
row2["Id"] = 2;
row2["Name"] = "佐藤";
row2["Age"] = 30;
table.Rows.Add(row2);

4-2. Rows.Addで簡単に行を追加する

Rows.Addを使うと、1行分の値をまとめて追加できます。

C#
table.Rows.Add(1, "田中", 25);
table.Rows.Add(2, "佐藤", 30);
table.Rows.Add(3, "鈴木", 28);

この方法は簡潔ですが、列の順番に依存します。

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

// Id, Name, Ageの順番で指定する
table.Rows.Add(1, "田中", 25);

列の順番を間違えると、型変換エラーや意図しないデータになります。

C#
// Name列に25、Age列に"田中"を入れようとしているため不正
// table.Rows.Add(1, 25, "田中");

安全性を重視するならNewRow()、簡潔さを重視するならRows.Add()を使うとよいでしょう。

4-3. 行・列の値を取得する

DataTableから値を取得するには、Rowsと列名を使います。

C#
DataRow row = table.Rows[0];

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

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

すべての行をループする場合は、foreachを使います。

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

列番号でも取得できます。

C#
Console.WriteLine(row[0]); // Id
Console.WriteLine(row[1]); // Name
Console.WriteLine(row[2]); // Age

ただし、列番号でのアクセスは列順に依存するため、保守性を考えると列名でアクセスする方がわかりやすいです。

4-4. Field<T>で型安全に値を取得する

Field<T>()を使うと、DataRowの値を型指定で取得できます。

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

Field<T>()は、NULL値を扱う場合にも便利です。

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

DBNull.Valueが入っている可能性がある列では、Nullable型を使うと例外を避けやすくなります。

C#
decimal? salary = row.Field<decimal?>("Salary");

if (salary.HasValue)
{
Console.WriteLine(salary.Value);
}
else
{
Console.WriteLine("給与は未設定です");
}

ToString()でも値は取得できますが、数値や日付として処理する場合はField<T>()を使う方が安全です。

4-5. 値を更新する

行の値を更新するには、対象のDataRowを取得して列に新しい値を代入します。

C#
DataRow row = table.Rows[0];

row["Name"] = "田中太郎";
row["Age"] = 26;

主キーを設定している場合は、Findで対象行を取得して更新できます。

C#
DataRow row = table.Rows.Find(1);

if (row != null)
{
row["Age"] = 26;
row["Department"] = "営業部";
}

条件に一致する複数行を更新する場合は、Selectで検索してからループします。

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

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

4-6. 行を削除する

行を削除する方法には、Delete()Rows.Remove()があります。

C#
DataRow row = table.Rows[0];
row.Delete();

Delete()は、行の状態を削除状態にします。AcceptChanges()を呼ぶと完全に反映されます。

C#
table.AcceptChanges();

一方、Rows.Remove()は行をコレクションから削除します。

C#
table.Rows.Remove(row);

条件に一致する行を削除する場合は、次のようにします。

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

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

table.AcceptChanges();

foreach (DataRow row in table.Rows)で直接削除すると、コレクション変更のエラーになることがあるため、Selectで配列にしてから削除すると安全です。

4-7. AcceptChanges・RejectChangesの使い方

DataTableは、行の追加・更新・削除状態を管理しています。

C#
DataTable table = CreateEmployeeTable();

DataRow row = table.Rows[0];
row["Age"] = 26;

Console.WriteLine(row.RowState); // Modified

AcceptChanges()を呼ぶと、現在の変更を確定します。

C#
table.AcceptChanges();

Console.WriteLine(row.RowState); // Unchanged

RejectChanges()を呼ぶと、変更を取り消します。

C#
DataTable table = CreateEmployeeTable();

DataRow row = table.Rows[0];
row["Age"] = 99;

table.RejectChanges();

Console.WriteLine(row["Age"]); // 元の値に戻る

追加した行も、RejectChanges()で取り消せます。

C#
DataRow newRow = table.NewRow();
newRow["Id"] = 10;
newRow["Name"] = "山田";
newRow["Department"] = "営業";
newRow["Age"] = 22;
newRow["Salary"] = 280000m;
newRow["JoinDate"] = DateTime.Today;

table.Rows.Add(newRow);

table.RejectChanges(); // 追加を取り消す

データベース更新と組み合わせる場合は、更新成功後にAcceptChanges()を呼ぶ、失敗したらRejectChanges()する、といった使い方があります。

4-8. Clone・Copy・ImportRowの違い

DataTableには、テーブルを複製するためのメソッドがあります。

メソッド内容
Clone()列定義や制約だけをコピーする。データ行はコピーしない
Copy()列定義とデータ行の両方をコピーする
ImportRow()別のDataTableから行を取り込む

Clone()の例です。

C#
DataTable original = CreateEmployeeTable();

DataTable clone = original.Clone();

Console.WriteLine(clone.Rows.Count); // 0
Console.WriteLine(clone.Columns.Count); // 元テーブルと同じ列数

Copy()の例です。

C#
DataTable original = CreateEmployeeTable();

DataTable copy = original.Copy();

Console.WriteLine(copy.Rows.Count); // 元テーブルと同じ行数

ImportRow()は、同じ構造のDataTableに行をコピーするときに使います。

C#
DataTable original = CreateEmployeeTable();
DataTable newTable = original.Clone();

DataRow row = original.Rows[0];
newTable.ImportRow(row);

Console.WriteLine(newTable.Rows.Count); // 1

検索結果だけを別のDataTableに移したい場合にも便利です。

C#
DataRow[] rows = original.Select("Department = '開発'");
DataTable result = original.Clone();

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

5. DataTableを検索・絞り込みする方法

DataTableでは、SelectメソッドやRows.Findを使ってデータを検索できます。

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

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

C#
DataTable table = CreateEmployeeTable();

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

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

数値条件も指定できます。

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

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

Selectの条件式はSQLに似ていますが、完全にSQLと同じではありません。DataColumn.Expressionの構文に近いルールで記述します。

5-2. 文字列・数値・日付・NULLを条件に検索する

文字列を条件にする場合は、値をシングルクォートで囲みます。

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

数値はそのまま記述します。

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

日付を条件にする場合は、#で囲む書き方がよく使われます。

C#
DataRow[] rows = table.Select("JoinDate >= #2021-01-01#");

NULLを検索する場合は、IS NULLを使います。

C#
DataRow[] rows = table.Select("Department IS NULL");

NULL以外を検索する場合は、IS NOT NULLを使います。

C#
DataRow[] rows = table.Select("Department IS NOT NULL");

= NULLのような書き方は期待通りに動かないため注意が必要です。

C#
// 非推奨
// table.Select("Department = NULL");

5-3. 複数条件で検索する

複数条件を指定する場合は、ANDORを使います。

C#
DataRow[] rows = table.Select("Department = '開発' AND Age >= 28");

どちらか一方の条件に一致すればよい場合はORを使います。

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

条件を組み合わせる場合は、括弧を使うと意図が明確になります。

C#
DataRow[] rows = table.Select("(Department = '開発' OR Department = '営業') AND Age >= 28");

給与が一定以上で、かつ入社日が指定日以降の社員を検索する例です。

C#
DataRow[] rows = table.Select("Salary >= 400000 AND JoinDate >= #2020-01-01#");

5-4. LIKEを使って部分一致検索する

文字列の部分一致検索にはLIKEを使います。

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

前方一致の例です。

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

後方一致の例です。

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

部署名に「開」を含む行を検索する例です。

C#
DataRow[] rows = table.Select("Department LIKE '%開%'");

ユーザー入力を条件に使う場合は、シングルクォートをエスケープします。

C#
string keyword = "田中";
string escaped = keyword.Replace("'", "''");

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

列名にスペースや記号が含まれる場合は、角括弧で囲みます。

C#
DataRow[] rows = table.Select("[Employee Name] LIKE '%田中%'");

5-5. Findメソッドで主キー検索する

主キーを設定している場合は、Rows.Findで検索できます。

C#
DataTable table = CreateEmployeeTable();

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

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

Findを使うには、事前にPrimaryKeyを設定しておく必要があります。

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

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

C#
table.PrimaryKey = new DataColumn[]
{
table.Columns["Department"],
table.Columns["Id"]
};

DataRow row = table.Rows.Find(new object[] { "開発", 2 });

主キーで1件を取得したい場合はFind、条件で複数行を取得したい場合はSelectを使うとよいでしょう。

5-6. 検索結果をDataTableに変換する

Selectの戻り値はDataRow[]です。検索結果をDataTableとして扱いたい場合は、CloneImportRowを使います。

C#
DataTable table = CreateEmployeeTable();

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

DataTable result = table.Clone();

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

LINQを使える場合は、CopyToDataTable()でも変換できます。

C#
DataTable result = table
.AsEnumerable()
.Where(row => row.Field<string>("Department") == "開発")
.CopyToDataTable();

ただし、CopyToDataTable()は対象データが0件の場合に例外になるため注意が必要です。

C#
var query = table
.AsEnumerable()
.Where(row => row.Field<string>("Department") == "存在しない部署");

DataTable result = query.Any()
? query.CopyToDataTable()
: table.Clone();

5-7. 条件式でエラーになりやすいポイント

DataTable.Selectの条件式では、次のような点でエラーになりやすいです。

文字列をクォートしていない例です。

C#
// エラーになりやすい
// table.Select("Name = 田中");

// 正しい
table.Select("Name = '田中'");

列名を間違えている例です。

C#
// Name列が存在しない場合はエラー
// table.Select("EmployeeName = '田中'");

日付の書き方が不正な例です。

C#
// エラーになりやすい
// table.Select("JoinDate >= '2021-01-01'");

// よく使われる書き方
table.Select("JoinDate >= #2021-01-01#");

NULL判定を=で行っている例です。

C#
// 非推奨
// table.Select("Department = NULL");

// 正しい
table.Select("Department IS NULL");

ユーザー入力にシングルクォートが含まれる例です。

C#
string keyword = "O'Brien";
string escaped = keyword.Replace("'", "''");

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

Select条件式は便利ですが、複雑になりすぎる場合はLINQで書いた方が読みやすくなります。

6. DataTableを並び替え・集計する方法

DataTableのデータは、SelectDataViewを使って並び替えできます。

6-1. Selectメソッドで並び替える

Selectメソッドには、条件式のほかに並び替え条件を指定できます。

C#
DataTable table = CreateEmployeeTable();

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

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

降順にする場合はDESCを指定します。

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

条件と並び替えを同時に指定できます。

C#
DataRow[] rows = table.Select("Department = '開発'", "Salary DESC");

6-2. DataView.Sortで並び替える

DataViewを使うと、DataTableの表示用ビューとして並び替えできます。

C#
DataTable table = CreateEmployeeTable();

DataView view = new DataView(table);
view.Sort = "Age ASC";

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

DataViewは、DataGridViewなどの表示と組み合わせると便利です。

C#
dataGridView1.DataSource = view;

並び替え条件を変更すれば、表示順も変わります。

C#
view.Sort = "Salary DESC";

6-3. 昇順・降順・複数列でソートする

昇順はASC、降順はDESCを使います。

C#
view.Sort = "Age ASC";
view.Sort = "Salary DESC";

複数列で並び替える場合は、カンマ区切りで指定します。

C#
view.Sort = "Department ASC, Age DESC";

これは、部署名の昇順で並び替え、同じ部署内では年齢の降順に並び替える指定です。

Selectでも同じように複数列でソートできます。

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

列名にスペースが含まれる場合は、角括弧で囲みます。

C#
view.Sort = "[Employee Name] ASC";

6-4. 並び替え後の結果をDataTableに変換する

DataViewで並び替えた結果をDataTableに変換するには、ToTable()を使います。

C#
DataTable table = CreateEmployeeTable();

DataView view = new DataView(table);
view.Sort = "Age ASC";

DataTable sortedTable = view.ToTable();

条件で絞り込んでから並び替えることもできます。

C#
DataView view = new DataView(table);
view.RowFilter = "Department = '開発'";
view.Sort = "Salary DESC";

DataTable result = view.ToTable();

Selectの結果をDataTableに戻す場合は、CloneImportRowを使います。

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

DataTable sortedTable = table.Clone();

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

6-5. Computeメソッドで合計・平均・件数を集計する

Computeメソッドを使うと、DataTableの列に対して集計できます。

合計を求める例です。

C#
object total = table.Compute("SUM(Salary)", "");

Console.WriteLine(total);

平均を求める例です。

C#
object average = table.Compute("AVG(Salary)", "");

Console.WriteLine(average);

最大値・最小値も求められます。

C#
object max = table.Compute("MAX(Salary)", "");
object min = table.Compute("MIN(Salary)", "");

Console.WriteLine($"最大: {max}, 最小: {min}");

条件を指定して集計することもできます。

C#
object totalDev = table.Compute("SUM(Salary)", "Department = '開発'");

Console.WriteLine(totalDev);

件数を数える場合は、COUNTを使います。

C#
object count = table.Compute("COUNT(Id)", "Department = '開発'");

Console.WriteLine(count);

ただし、複雑な集計やグループ化はComputeよりLINQの方が書きやすいです。

6-6. DataTable自体を直接ソートできない理由

DataTableには、List<T>.Sort()のようにテーブル本体を直接並び替えるメソッドはありません。

C#
// DataTableにはSortメソッドがない
// table.Sort();

これは、DataTableがデータ本体を保持する構造であり、並び替えは表示や抽出のためのビューとして扱う設計になっているためです。

そのため、並び替えたい場合は次のいずれかを使います。

C#
// Selectで並び替え
DataRow[] rows = table.Select("", "Age ASC");

// DataViewで並び替え
DataView view = new DataView(table);
view.Sort = "Age ASC";

// LINQで並び替え
var query = table.AsEnumerable()
.OrderBy(row => row.Field<int>("Age"));

画面表示だけ並び替えたい場合はDataView、処理結果として新しいDataTableが欲しい場合はToTable()CopyToDataTable()を使うとよいでしょう。

7. LINQでDataTableを操作する

C#では、DataTableをLINQで操作できます。Selectメソッドよりも型安全で、複雑な条件や集計を読みやすく書ける場合があります。

7-1. AsEnumerableを使う準備

DataTableにLINQを使うには、AsEnumerable()を使います。

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

基本形は次のとおりです。

C#
DataTable table = CreateEmployeeTable();

var query = table.AsEnumerable();

AsEnumerable()を使うと、DataRowをLINQで処理できるようになります。

C#
foreach (DataRow row in table.AsEnumerable())
{
Console.WriteLine(row.Field<string>("Name"));
}

Field<T>()を組み合わせることで、列の値を型指定で取得できます。

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

7-2. LINQで検索・絞り込みする

LINQで条件に一致する行を検索するには、Whereを使います。

C#
DataTable table = CreateEmployeeTable();

var query = table.AsEnumerable()
.Where(row => row.Field<string>("Department") == "開発");

foreach (DataRow row in query)
{
Console.WriteLine($"{row.Field<string>("Name")}: {row.Field<string>("Department")}");
}

数値条件の例です。

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

複数条件の例です。

C#
var query = table.AsEnumerable()
.Where(row =>
row.Field<string>("Department") == "開発" &&
row.Field<decimal>("Salary") >= 400000m);

部分一致検索は、Containsを使うとわかりやすいです。

C#
string keyword = "田";

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

NULLの可能性がある場合は、null条件演算子を使います。

C#
var query = table.AsEnumerable()
.Where(row => row.Field<string>("Department")?.Contains("開") == true);

7-3. LINQで並び替える

LINQで並び替えるには、OrderByOrderByDescendingを使います。

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

降順にする場合は、OrderByDescendingを使います。

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

複数列で並び替える場合は、ThenByThenByDescendingを使います。

C#
var query = table.AsEnumerable()
.OrderBy(row => row.Field<string>("Department"))
.ThenByDescending(row => row.Field<int>("Age"));

結果を表示する例です。

C#
foreach (DataRow row in query)
{
Console.WriteLine($"{row.Field<string>("Department")} - {row.Field<string>("Name")} - {row.Field<int>("Age")}");
}

7-4. LINQで必要な列だけ取得する

LINQでは、必要な列だけを匿名型として取り出せます。

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

foreach (var item in query)
{
Console.WriteLine($"{item.Id}: {item.Name} ({item.Department})");
}

特定の列だけをリストにすることもできます。

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

重複を除外する場合は、Distinctを使います。

C#
List<string> departments = table.AsEnumerable()
.Select(row => row.Field<string>("Department"))
.Distinct()
.ToList();

ただし、匿名型の結果はそのままDataTableには戻しにくいため、必要に応じて新しいDataTableを作成します。

C#
DataTable result = new DataTable();
result.Columns.Add("Name", typeof(string));
result.Columns.Add("Department", typeof(string));

foreach (var item in query)
{
result.Rows.Add(item.Name, item.Department);
}

7-5. LINQでグループ化・集計する

LINQを使うと、部署ごとの人数や平均給与を簡単に集計できます。

C#
var query = 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")),
AverageSalary = group.Average(row => row.Field<decimal>("Salary"))
});

結果を表示します。

C#
foreach (var item in query)
{
Console.WriteLine($"{item.Department}: {item.Count}人, 合計給与={item.TotalSalary}, 平均給与={item.AverageSalary}");
}

部署ごとの最大年齢を取得する例です。

C#
var maxAgeByDepartment = table.AsEnumerable()
.GroupBy(row => row.Field<string>("Department"))
.Select(group => new
{
Department = group.Key,
MaxAge = group.Max(row => row.Field<int>("Age"))
});

DataTable.Computeでも集計はできますが、グループ化を伴う集計はLINQの方が書きやすくなります。

7-6. LINQの結果をDataTableに戻す

LINQで絞り込んだDataRowの集合は、CopyToDataTable()DataTableに戻せます。

C#
var query = table.AsEnumerable()
.Where(row => row.Field<string>("Department") == "開発");

DataTable result = query.CopyToDataTable();

ただし、検索結果が0件の場合は例外になります。

C#
var query = table.AsEnumerable()
.Where(row => row.Field<string>("Department") == "存在しない部署");

DataTable result = query.Any()
? query.CopyToDataTable()
: table.Clone();

並び替えた結果をDataTableに戻す場合も同じです。

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

DataTable sortedTable = query.Any()
? query.CopyToDataTable()
: table.Clone();

匿名型に変換した結果をDataTableにしたい場合は、自分で列定義を作成します。

C#
var query = table.AsEnumerable()
.Select(row => new
{
Name = row.Field<string>("Name"),
Salary = row.Field<decimal>("Salary")
});

DataTable result = new DataTable();
result.Columns.Add("Name", typeof(string));
result.Columns.Add("Salary", typeof(decimal));

foreach (var item in query)
{
result.Rows.Add(item.Name, item.Salary);
}

7-7. SelectメソッドとLINQの使い分け

DataTable.SelectとLINQは、どちらも検索や並び替えに使えます。

Selectが向いているケースは以下です。

  • 条件式を文字列で動的に作りたい

  • 簡単な検索だけ行いたい

  • 既存コードがDataTable.Select中心で書かれている

  • DataView.RowFilterと似た条件式で扱いたい

LINQが向いているケースは以下です。

  • 型安全に値を取得したい

  • 複雑な条件をC#の構文で書きたい

  • グループ化や集計を行いたい

  • List<T>など他のコレクションと同じ感覚で扱いたい

  • 可読性を高めたい

簡単な条件ならSelectでも十分です。

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

複雑な条件や集計ならLINQが便利です。

C#
var query = table.AsEnumerable()
.Where(row => row.Field<int>("Age") >= 30)
.GroupBy(row => row.Field<string>("Department"))
.Select(group => new
{
Department = group.Key,
Count = group.Count()
});

実務では、画面表示や簡単なフィルタにはDataView、処理ロジックにはLINQ、既存のDataTable操作にはSelectというように使い分けるとよいでしょう。

8. DataTableをDataGridViewに表示する方法

Windows Formsで一覧画面を作る場合、DataTableDataGridViewの組み合わせは非常に便利です。

8-1. DataGridViewのDataSourceにDataTableを設定する

DataGridViewDataTableを表示するには、DataSourceに設定します。

C#
DataTable table = CreateEmployeeTable();

dataGridView1.DataSource = table;

これだけで、DataTableの列と行が自動的に表示されます。

DataViewを表示することもできます。

C#
DataView view = new DataView(table);
view.Sort = "Age ASC";

dataGridView1.DataSource = view;

BindingSourceを使う場合は、次のように設定します。

C#
BindingSource bindingSource = new BindingSource();
bindingSource.DataSource = table;

dataGridView1.DataSource = bindingSource;

8-2. Windows Formsで一覧表示するサンプル

フォームのLoadイベントでDataTableを作成し、DataGridViewに表示する例です。

C#
using System;
using System.Data;
using System.Windows.Forms;

public partial class Form1 : Form
{
private DataTable _table;

public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
_table = CreateEmployeeTable();
dataGridView1.DataSource = _table;
}

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

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

table.Rows.Add(1, "田中", "営業", 25, 300000m);
table.Rows.Add(2, "佐藤", "開発", 30, 420000m);
table.Rows.Add(3, "鈴木", "開発", 28, 390000m);

return table;
}
}

このコードで、フォーム起動時に社員一覧がDataGridViewに表示されます。

8-3. 列幅・ヘッダー名・表示順を設定する

DataGridViewでは、列幅やヘッダー名を設定できます。

C#
dataGridView1.Columns["Id"].HeaderText = "社員ID";
dataGridView1.Columns["Name"].HeaderText = "氏名";
dataGridView1.Columns["Department"].HeaderText = "部署";
dataGridView1.Columns["Age"].HeaderText = "年齢";
dataGridView1.Columns["Salary"].HeaderText = "給与";

列幅を設定する例です。

C#
dataGridView1.Columns["Id"].Width = 80;
dataGridView1.Columns["Name"].Width = 120;
dataGridView1.Columns["Department"].Width = 120;

表示順を変更するには、DisplayIndexを使います。

C#
dataGridView1.Columns["Name"].DisplayIndex = 0;
dataGridView1.Columns["Department"].DisplayIndex = 1;
dataGridView1.Columns["Age"].DisplayIndex = 2;

特定の列を非表示にすることもできます。

C#
dataGridView1.Columns["Salary"].Visible = false;

自動で列幅を調整する場合は、次のようにします。

C#
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;

8-4. DataGridViewで編集した内容をDataTableに反映する

DataGridViewDataTableをバインドしている場合、セルを編集すると基本的にDataTableにも反映されます。

C#
dataGridView1.DataSource = _table;

編集後の値を確認する例です。

C#
foreach (DataRow row in _table.Rows)
{
Console.WriteLine($"{row["Name"]}: {row["Age"]}");
}

編集を確定させたい場合は、EndEdit()を呼ぶことがあります。

C#
dataGridView1.EndEdit();

BindingSourceを使っている場合は、次のようにします。

C#
bindingSource.EndEdit();

変更状態を確定するには、AcceptChanges()を呼びます。

C#
_table.AcceptChanges();

ただし、データベース更新と連携する場合は、DB更新前にAcceptChanges()を呼ぶと変更状態が消えてしまうため注意が必要です。DB更新が成功してからAcceptChanges()を呼ぶのが一般的です。

8-5. BindingSourceを使って表示・検索・並び替えを扱いやすくする

BindingSourceを使うと、DataGridViewとの連携が扱いやすくなります。

C#
private DataTable _table;
private BindingSource _bindingSource;

private void Form1_Load(object sender, EventArgs e)
{
_table = CreateEmployeeTable();

_bindingSource = new BindingSource();
_bindingSource.DataSource = _table;

dataGridView1.DataSource = _bindingSource;
}

BindingSource.Filterで絞り込みできます。

C#
_bindingSource.Filter = "Department = '開発'";

部分一致検索もできます。

C#
string keyword = "田";
string escaped = keyword.Replace("'", "''");

_bindingSource.Filter = $"Name LIKE '%{escaped}%'";

フィルタを解除するには、Filternullを設定します。

C#
_bindingSource.Filter = null;

並び替えはSortで指定します。

C#
_bindingSource.Sort = "Age DESC";

BindingSourceを使うと、検索・並び替え・編集を画面側で扱いやすくなります。

8-6. DataGridViewでよくある表示トラブル

DataGridViewに表示されない場合は、まずDataSourceが正しく設定されているか確認します。

C#
dataGridView1.DataSource = table;

DataTableに列や行が入っているかも確認します。

C#
Console.WriteLine(table.Columns.Count);
Console.WriteLine(table.Rows.Count);

フォームのLoadイベントが登録されていない場合も表示されません。

C#
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.DataSource = CreateEmployeeTable();
}

列を手動で追加している場合は、AutoGenerateColumnsの設定に注意します。

C#
dataGridView1.AutoGenerateColumns = true;

既に列を手動定義している場合は、DataPropertyNameを設定する必要があります。

C#
dataGridViewTextBoxColumn1.DataPropertyName = "Name";

別スレッドからDataGridViewを更新している場合は、UIスレッドで更新する必要があります。

C#
this.Invoke(new Action(() =>
{
dataGridView1.DataSource = table;
}));

9. データベースとDataTableを連携する

DataTableは、データベースとの連携でもよく使われます。DataAdapterを使うと、SQLの検索結果をDataTableに格納できます。

9-1. DataAdapterでデータベースの結果をDataTableに格納する

SQL Serverからデータを取得してDataTableに格納する例です。

C#
using System.Data;
using System.Data.SqlClient;

string connectionString = "接続文字列を指定";
string sql = "SELECT Id, Name, Department, Age FROM Employees";

DataTable table = new DataTable();

using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlDataAdapter adapter = new SqlDataAdapter(sql, connection))
{
adapter.Fill(table);
}

取得したDataTableは、そのままDataGridViewに表示できます。

C#
dataGridView1.DataSource = table;

条件付きで検索する場合は、SQL側でWHEREを使います。

C#
string sql = "SELECT Id, Name, Department, Age FROM Employees WHERE Department = @Department";

using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlDataAdapter adapter = new SqlDataAdapter(sql, connection))
{
adapter.SelectCommand.Parameters.AddWithValue("@Department", "開発");
adapter.Fill(table);
}

9-2. DataTableの内容をデータベースに更新する

SqlDataAdapterSqlCommandBuilderを使うと、DataTableの変更をデータベースに反映できます。

C#
string connectionString = "接続文字列を指定";
string sql = "SELECT Id, Name, Department, Age FROM Employees";

DataTable table = new DataTable();

using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlDataAdapter adapter = new SqlDataAdapter(sql, connection))
using (SqlCommandBuilder builder = new SqlCommandBuilder(adapter))
{
adapter.Fill(table);

DataRow row = table.Rows[0];
row["Age"] = 31;

adapter.Update(table);
}

ただし、実務では自動生成されたSQLに頼りすぎず、InsertCommandUpdateCommandDeleteCommandを明示的に設定することも多いです。

C#
adapter.UpdateCommand = new SqlCommand(
"UPDATE Employees SET Name = @Name, Age = @Age WHERE Id = @Id",
connection);

更新処理では、主キーが正しく取得されていること、変更状態が残っていること、DB更新前にAcceptChanges()を呼んでいないことが重要です。

9-3. SQLの検索結果をDataGridViewに表示する

SQLの検索結果をDataGridViewに表示する基本例です。

C#
private void LoadEmployees()
{
string connectionString = "接続文字列を指定";
string sql = "SELECT Id, Name, Department, Age FROM Employees";

DataTable table = new DataTable();

using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlDataAdapter adapter = new SqlDataAdapter(sql, connection))
{
adapter.Fill(table);
}

dataGridView1.DataSource = table;
}

ボタンクリックで検索する例です。

C#
private void buttonSearch_Click(object sender, EventArgs e)
{
string keyword = textBoxKeyword.Text;

string connectionString = "接続文字列を指定";
string sql = "SELECT Id, Name, Department, Age FROM Employees WHERE Name LIKE @Name";

DataTable table = new DataTable();

using (SqlConnection connection = new SqlConnection(connectionString))
using (SqlDataAdapter adapter = new SqlDataAdapter(sql, connection))
{
adapter.SelectCommand.Parameters.AddWithValue("@Name", "%" + keyword + "%");
adapter.Fill(table);
}

dataGridView1.DataSource = table;
}

ユーザー入力をSQLに直接連結するとSQLインジェクションの危険があるため、必ずパラメータを使います。

C#
adapter.SelectCommand.Parameters.AddWithValue("@Name", "%" + keyword + "%");

9-4. DB取得後にDataTable側で検索・並び替えする

データベースから取得した後、DataTable側でさらに検索・並び替えできます。

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

DataViewを使う場合は、次のようにします。

C#
DataView view = new DataView(table);
view.RowFilter = "Department = '開発'";
view.Sort = "Age DESC";

dataGridView1.DataSource = view;

BindingSourceを使う場合です。

C#
BindingSource source = new BindingSource();
source.DataSource = table;

source.Filter = "Department = '開発'";
source.Sort = "Age DESC";

dataGridView1.DataSource = source;

ただし、大量データの場合は、できるだけSQL側で絞り込みや並び替えを行う方が効率的です。

SQL
SELECT Id, Name, Department, Age
FROM Employees
WHERE Department = @Department
ORDER BY Age DESC

9-5. 大量データを扱うときの注意点

DataTableはメモリ上にデータを保持するため、大量データを詰め込みすぎるとメモリ使用量が増えます。

大量データを扱う場合は、次の点に注意します。

  • 必要な列だけ取得する

  • 必要な行だけ取得する

  • SQL側でWHEREORDER BYを使う

  • ページングを検討する

  • DataTableにすべて読み込まない

  • 表示件数を制限する

  • 集計は可能ならDB側で行う

悪い例です。

SQL
SELECT * FROM Employees

必要な列だけ取得する例です。

SQL
SELECT Id, Name, Department, Age
FROM Employees
WHERE Department = @Department
ORDER BY Name

ページングの例です。

SQL
SELECT Id, Name, Department, Age
FROM Employees
ORDER BY Id
OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY

DataTableは便利ですが、数十万件、数百万件のデータをすべて読み込む用途には向いていません。大量データでは、DB側で絞り込んでから必要な分だけ取得する設計が重要です。

10. DataTableでよくあるエラーと対処法

C#でDataTableを使っていると、列名、型変換、NULL、検索条件、画面表示などでエラーが発生しやすいです。

10-1. 列名が存在しないエラー

存在しない列名を指定するとエラーになります。

C#
// Name列が存在しない場合はエラー
string name = row["Name"].ToString();

対処法として、列が存在するか確認します。

C#
if (table.Columns.Contains("Name"))
{
string name = row["Name"].ToString();
}

列名のタイプミスを防ぐには、定数化すると効果的です。

C#
public static class EmployeeColumns
{
public const string Id = "Id";
public const string Name = "Name";
public const string Age = "Age";
}

使用例です。

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

10-2. 型変換エラー

DataColumnで指定した型と異なる値を入れると、型変換エラーが発生することがあります。

C#
table.Columns.Add("Age", typeof(int));

// Age列はint型なので不正
// table.Rows.Add("三十");

値を取得するときも、実際の型とキャストが合っていないとエラーになります。

C#
// Age列がint型でない場合はエラーになる可能性
int age = (int)row["Age"];

安全に変換するには、Field<T>()Convertを使います。

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

文字列から数値に変換する場合は、int.TryParseを使います。

C#
string value = row["Age"].ToString();

if (int.TryParse(value, out int age))
{
Console.WriteLine(age);
}
else
{
Console.WriteLine("年齢が数値ではありません");
}

10-3. DBNullで例外が発生する

DataTableでは、NULL値はC#のnullではなくDBNull.Valueとして扱われることがあります。

C#
if (row["Name"] == DBNull.Value)
{
Console.WriteLine("NameはNULLです");
}

ToString()DBNull.Valueでも空文字に近い結果になりますが、数値や日付にキャストすると例外になる可能性があります。

C#
// DBNullの場合は例外になる可能性
// int age = (int)row["Age"];

対処法として、IsNullを使います。

C#
if (!row.IsNull("Age"))
{
int age = row.Field<int>("Age");
}

Nullable型で取得する方法もあります。

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

if (age.HasValue)
{
Console.WriteLine(age.Value);
}

文字列の場合は、次のように安全に取得できます。

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

10-4. CopyToDataTableでデータがないとエラーになる

LINQの結果をCopyToDataTable()で変換するとき、対象データが0件だと例外になります。

C#
var query = table.AsEnumerable()
.Where(row => row.Field<string>("Department") == "存在しない部署");

// 0件の場合はエラー
// DataTable result = query.CopyToDataTable();

対処法は、Any()で確認してから変換することです。

C#
DataTable result = query.Any()
? query.CopyToDataTable()
: table.Clone();

ただし、Any()を呼ぶとクエリが一度評価されます。必要に応じてToList()にしてから使う方法もあります。

C#
var list = query.ToList();

DataTable result = list.Any()
? list.CopyToDataTable()
: table.Clone();

10-5. Select条件式の書き方でエラーになる

Select条件式では、文字列、日付、NULL、列名の指定に注意が必要です。

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

C#
table.Select("Name = '田中'");

日付は#で囲みます。

C#
table.Select("JoinDate >= #2021-01-01#");

NULLはIS NULLを使います。

C#
table.Select("Department IS NULL");

列名にスペースがある場合は角括弧で囲みます。

C#
table.Select("[Employee Name] = '田中'");

ユーザー入力を使う場合は、シングルクォートをエスケープします。

C#
string keyword = textBoxKeyword.Text;
string escaped = keyword.Replace("'", "''");

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

条件式が複雑になる場合は、LINQで書く方が安全で読みやすくなります。

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

10-6. DataGridViewに表示されない

DataGridViewDataTableが表示されない場合は、以下を確認します。

まず、DataSourceを設定しているか確認します。

C#
dataGridView1.DataSource = table;

テーブルにデータが入っているか確認します。

C#
Console.WriteLine(table.Columns.Count);
Console.WriteLine(table.Rows.Count);

自動列生成が無効になっていないか確認します。

C#
dataGridView1.AutoGenerateColumns = true;

手動列を使っている場合は、DataPropertyNameを確認します。

C#
columnName.DataPropertyName = "Name";

フォームのイベントが正しく登録されているかも確認します。

C#
private void Form1_Load(object sender, EventArgs e)
{
dataGridView1.DataSource = CreateEmployeeTable();
}

DataTableを作成しただけで、DataSourceに設定していないケースもよくあります。

C#
DataTable table = CreateEmployeeTable();

// これが必要
dataGridView1.DataSource = table;

11. DataTableを使うときの実践的なベストプラクティス

DataTableは便利ですが、使い方によっては保守しにくいコードになりやすいです。実務で使う場合は、いくつかのポイントを意識すると安全です。

11-1. 列名を定数化する

DataTableでは列名を文字列で指定するため、タイプミスが実行時エラーにつながります。

C#
row["Name"]

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

C#
public static class EmployeeColumns
{
public const string Id = "Id";
public const string Name = "Name";
public const string Department = "Department";
public const string Age = "Age";
public const string Salary = "Salary";
public const string JoinDate = "JoinDate";
}

使用例です。

C#
int id = row.Field<int>(EmployeeColumns.Id);
string name = row.Field<string>(EmployeeColumns.Name);

列追加時にも同じ定数を使うと、列名の不一致を防げます。

C#
table.Columns.Add(EmployeeColumns.Id, typeof(int));
table.Columns.Add(EmployeeColumns.Name, typeof(string));
table.Columns.Add(EmployeeColumns.Age, typeof(int));

11-2. 型を意識して値を取得する

row["Age"]の戻り値はobjectです。そのため、型を意識して取得することが大切です。

C#
int age = row.Field<int>("Age");
decimal salary = row.Field<decimal>("Salary");
DateTime joinDate = row.Field<DateTime>("JoinDate");

ToString()だけに頼ると、数値や日付の処理で不具合が起きやすくなります。

C#
// 表示だけならよい
string ageText = row["Age"].ToString();

// 計算するなら型で取得する
int age = row.Field<int>("Age");

金額計算ではdecimalを使うのが一般的です。

C#
decimal salary = row.Field<decimal>("Salary");
decimal bonus = salary * 0.1m;

11-3. NULLチェックを必ず行う

DataTableでは、NULL値がDBNull.Valueとして入ることがあります。

C#
if (row.IsNull("Salary"))
{
Console.WriteLine("給与は未設定です");
}

Nullable型で受け取る方法も便利です。

C#
decimal? salary = row.Field<decimal?>("Salary");

if (salary.HasValue)
{
Console.WriteLine(salary.Value);
}

文字列の場合も、NULLの可能性があるならチェックします。

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

LINQで検索する場合も、NULL対策を入れると安全です。

C#
var query = table.AsEnumerable()
.Where(row => row.Field<string>("Department")?.Contains("開発") == true);

11-4. 大量データではDataTableに詰め込みすぎない

DataTableはメモリ上にデータを保持するため、大量データの扱いには注意が必要です。

大量データでは、次のような設計を検討します。

  • SQL側で必要なデータだけ取得する

  • SELECT *を避ける

  • ページングする

  • 表示件数を制限する

  • 集計はDB側で行う

  • ストリーミング処理を検討する

  • 必要に応じてList<T>や専用モデルを使う

悪い例です。

C#
string sql = "SELECT * FROM HugeTable";

改善例です。

C#
string sql = @"
SELECT Id, Name, Department
FROM Employees
WHERE Department = @Department
ORDER BY Id
OFFSET @Offset ROWS FETCH NEXT @PageSize ROWS ONLY";

DataTableは便利ですが、すべてのデータを読み込む万能コンテナとして使うのではなく、用途に応じて必要な範囲で使うことが重要です。

11-5. DataTableとList<T>を使い分ける

DataTableList<T>は、それぞれ得意な用途が異なります。

DataTableが向いている例です。

C#
DataTable table = new DataTable();
  • DB結果をそのまま扱う

  • DataGridViewに表示する

  • 列構成が動的に変わる

  • CSVやExcelを汎用的に扱う

  • DataAdapterと連携する

List<T>が向いている例です。

C#
List<Employee> employees = new List<Employee>();
  • 型安全に扱いたい

  • ビジネスロジックを実装したい

  • Web APIでJSONとして返したい

  • ドメインモデルとして扱いたい

  • テストしやすいコードにしたい

業務アプリでは、DB取得直後や画面表示ではDataTable、ビジネスロジックではList<T>やクラスに変換する、という使い分けも有効です。

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

11-6. 保守しやすいサンプルコードの書き方

保守しやすいDataTableコードにするには、テーブル作成処理や列名を整理します。

C#
public static class EmployeeTableFactory
{
public static DataTable Create()
{
DataTable table = new DataTable("Employees");

table.Columns.Add(EmployeeColumns.Id, typeof(int));
table.Columns.Add(EmployeeColumns.Name, typeof(string));
table.Columns.Add(EmployeeColumns.Department, typeof(string));
table.Columns.Add(EmployeeColumns.Age, typeof(int));
table.Columns.Add(EmployeeColumns.Salary, typeof(decimal));
table.Columns.Add(EmployeeColumns.JoinDate, typeof(DateTime));

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

return table;
}
}

行追加もメソッド化できます。

C#
public static void AddEmployee(
DataTable table,
int id,
string name,
string department,
int age,
decimal salary,
DateTime joinDate)
{
table.Rows.Add(id, name, department, age, salary, joinDate);
}

使用例です。

C#
DataTable table = EmployeeTableFactory.Create();

AddEmployee(table, 1, "田中", "営業", 25, 300000m, new DateTime(2022, 4, 1));
AddEmployee(table, 2, "佐藤", "開発", 30, 420000m, new DateTime(2020, 10, 1));

列名をベタ書きせず、テーブル作成や行追加を共通化することで、後から列を追加・変更するときの修正漏れを減らせます。

12. C# DataTableのよくある質問

ここでは、C#のDataTableに関するよくある質問をまとめます。

12-1. DataTableとList<T>はどちらを使うべき?

用途によって使い分けます。

DataTableは、データベース結果や画面表示、CSV・Excelのような表形式データを柔軟に扱いたい場合に向いています。

C#
DataTable table = new DataTable();

List<T>は、型安全にデータを扱いたい場合や、ビジネスロジックを実装したい場合に向いています。

C#
List<Employee> employees = new List<Employee>();

単純な一覧表示やDB結果の受け皿ならDataTable、アプリケーションの中心的なデータモデルとして扱うならList<T>を選ぶとよいでしょう。

12-2. DataTableをCSVに出力できる?

できます。DataTableの列と行をループしてCSV文字列を作成します。

C#
using System.Text;

public static string ToCsv(DataTable table)
{
StringBuilder sb = new StringBuilder();

string[] columnNames = table.Columns
.Cast<DataColumn>()
.Select(column => EscapeCsv(column.ColumnName))
.ToArray();

sb.AppendLine(string.Join(",", columnNames));

foreach (DataRow row in table.Rows)
{
string[] fields = table.Columns
.Cast<DataColumn>()
.Select(column => EscapeCsv(row[column]?.ToString() ?? ""))
.ToArray();

sb.AppendLine(string.Join(",", fields));
}

return sb.ToString();
}

private static string EscapeCsv(string value)
{
if (value.Contains(",") || value.Contains("\"") || value.Contains("\n"))
{
value = value.Replace("\"", "\"\"");
return $"\"{value}\"";
}

return value;
}

ファイルに保存する例です。

C#
File.WriteAllText("employees.csv", ToCsv(table), Encoding.UTF8);

12-3. DataTableをExcelに出力できる?

できます。方法はいくつかあります。

簡易的にはCSVとして出力し、Excelで開く方法があります。

C#
File.WriteAllText("employees.csv", ToCsv(table), Encoding.UTF8);

本格的に.xlsx形式で出力したい場合は、Excel操作用のライブラリを使う方法が一般的です。

基本的な考え方は、DataTableの列をExcelの1行目に出力し、行データを2行目以降に書き込む形です。

C#
foreach (DataColumn column in table.Columns)
{
Console.WriteLine(column.ColumnName);
}

foreach (DataRow row in table.Rows)
{
foreach (DataColumn column in table.Columns)
{
Console.WriteLine(row[column]);
}
}

Windows環境でExcelがインストールされている場合はCOM操作も可能ですが、サーバーサイドやWebアプリでは推奨されないことが多いため、専用ライブラリを使う方が扱いやすいです。

12-4. DataTableをJSONに変換できる?

できます。よくある方法は、DataTableList<Dictionary<string, object>>に変換してからJSON化する方法です。

C#
using System.Text.Json;

public static string ToJson(DataTable table)
{
var rows = new List<Dictionary<string, object>>();

foreach (DataRow row in table.Rows)
{
var dict = new Dictionary<string, object>();

foreach (DataColumn column in table.Columns)
{
dict[column.ColumnName] = row[column] == DBNull.Value ? null : row[column];
}

rows.Add(dict);
}

return JsonSerializer.Serialize(rows);
}

使用例です。

C#
string json = ToJson(table);
Console.WriteLine(json);

クラスに変換してからJSON化する方法もあります。

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

string json = JsonSerializer.Serialize(employees);

Web APIなどでは、DataTableを直接返すより、DTOやモデルクラスに変換して返す方が扱いやすい場合が多いです。

12-5. DataTableの行数・列数を取得するには?

行数はRows.Countで取得できます。

C#
int rowCount = table.Rows.Count;

列数はColumns.Countで取得できます。

C#
int columnCount = table.Columns.Count;

使用例です。

C#
Console.WriteLine($"行数: {table.Rows.Count}");
Console.WriteLine($"列数: {table.Columns.Count}");

列名を一覧表示する場合は、Columnsをループします。

C#
foreach (DataColumn column in table.Columns)
{
Console.WriteLine(column.ColumnName);
}

行と列をすべて表示する例です。

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

Console.WriteLine();
}

12-6. DataTableは現在でも使うべき?

DataTableは現在でも使われています。特にWindows Forms、既存の業務アプリ、データベース連携、帳票、CSV・Excel処理では今でも便利です。

ただし、新規開発ではすべてをDataTableで処理するのではなく、用途に応じて使い分けることが重要です。

DataTableを使う価値がある場面は以下です。

  • DataGridViewに簡単に表示したい

  • データベースの検索結果をそのまま扱いたい

  • 既存システムがDataTable中心で作られている

  • 列構成が動的に変わる

  • 表形式データを汎用的に処理したい

一方、次のような場合はList<T>やクラスを使う方が向いています。

  • 型安全に処理したい

  • ビジネスロジックを明確にしたい

  • Web APIでJSONを扱う

  • テストしやすい設計にしたい

  • ドメインモデルを中心に設計したい

つまり、DataTableは古いから使わないというものではなく、表形式データの受け皿や画面表示用として使うと効果的です。

まとめ

C#のDataTableは、表形式のデータをメモリ上で扱うための便利なクラスです。列を定義し、行を追加し、検索・並び替え・集計を行い、DataGridViewに表示したり、データベースと連携したりできます。

基本的な作成方法は次のとおりです。

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

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);

値を取得する場合は、Field<T>()を使うと型を意識したコードになります。

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

検索にはSelectFindを使えます。

C#
DataRow[] rows = table.Select("Age >= 30");
DataRow row = table.Rows.Find(1);

並び替えにはSelectDataView、LINQを使えます。

C#
DataView view = new DataView(table);
view.Sort = "Age DESC";

LINQを使えば、より柔軟に検索・並び替え・集計できます。

C#
var query = table.AsEnumerable()
.Where(row => row.Field<int>("Age") >= 30)
.OrderBy(row => row.Field<string>("Name"));

Windows Formsでは、DataGridViewDataSourceに設定するだけで一覧表示できます。

C#
dataGridView1.DataSource = table;

DataTableは便利な一方で、列名を文字列で扱うためタイプミスに弱く、DBNullや型変換にも注意が必要です。保守性を高めるには、列名の定数化、Field<T>()の活用、NULLチェック、List<T>との使い分けを意識しましょう。

C#でデータベース結果や一覧画面、CSV・Excelのような表形式データを扱う場合、DataTableを正しく理解しておくと、実務で非常に役立ちます。