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を理解するには、関連するクラスとの違いを押さえることが重要です。
| クラス | 役割 |
|---|---|
DataTable | 1つの表を表す |
DataSet | 複数のDataTableをまとめて管理する |
DataRow | DataTable内の1行を表す |
DataColumn | DataTable内の1列を表す |
DataView | DataTableの表示用ビュー。検索や並び替えに使う |
関係性を簡単に表すと、次のようになります。
DataSet
├─ DataTable
│ ├─ DataColumn
│ ├─ DataColumn
│ ├─ DataRow
│ └─ DataRow
└─ DataTable
├─ DataColumn
└─ DataRow
DataTableは1つのテーブルです。複数のテーブルをまとめたい場合はDataSetを使います。
DataRowは行データ、DataColumnは列定義です。DataViewはDataTableのデータを並び替えたり、絞り込んだりして表示するためのビューです。
1-3. DataTableがよく使われる場面
C#のDataTableは、特に次のような場面で使われます。
C#// データベースから取得した結果をDataTableに格納する例
DataTable table = new DataTable();
代表的な利用シーンは以下です。
SQLの検索結果を格納する
Windows Formsの
DataGridViewに一覧表示するCSVやExcelから読み込んだデータを一時的に保持する
データを条件検索・並び替えして表示する
帳票出力やファイル出力の前処理として使う
複数のデータソースを結合・加工する
業務アプリケーションでは、データベースと画面表示の間にDataTableを挟む構成がよく使われます。
1-4. DataTableを使うメリット・デメリット
DataTableには便利な点が多い一方で、注意点もあります。
メリットは次のとおりです。
表形式のデータを扱いやすい
列名でデータにアクセスできる
DataGridViewにそのまま表示できるSelectやComputeで検索・集計ができるデータベース連携と相性がよい
スキーマ情報を持てる
主キーや一意制約を設定できる
一方、デメリットもあります。
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では、作成したDataTableをDataGridViewのDataSourceに設定します。
C#dataGridView1.DataSource = table;
これだけで、DataTableの内容を一覧表示できます。
2-3. サンプルで使用するDataTableの完成イメージ
この記事では、主に社員情報を例にして解説します。
完成イメージは次のようなDataTableです。
| Id | Name | Department | Age | Salary | JoinDate |
|---|---|---|---|---|---|
| 1 | 田中 | 営業 | 25 | 300000 | 2022/04/01 |
| 2 | 佐藤 | 開発 | 30 | 420000 | 2020/10/01 |
| 3 | 鈴木 | 開発 | 28 | 390000 | 2021/07/15 |
| 4 | 高橋 | 総務 | 35 | 450000 | 2019/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 | 名前、住所、コードなど |
int | ID、年齢、数量など |
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. 複数条件で検索する
複数条件を指定する場合は、ANDやORを使います。
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として扱いたい場合は、CloneとImportRowを使います。
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のデータは、SelectやDataViewを使って並び替えできます。
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に戻す場合は、CloneとImportRowを使います。
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で並び替えるには、OrderByやOrderByDescendingを使います。
C#var query = table.AsEnumerable()
.OrderBy(row => row.Field<int>("Age"));
降順にする場合は、OrderByDescendingを使います。
C#var query = table.AsEnumerable()
.OrderByDescending(row => row.Field<decimal>("Salary"));
複数列で並び替える場合は、ThenByやThenByDescendingを使います。
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で一覧画面を作る場合、DataTableとDataGridViewの組み合わせは非常に便利です。
8-1. DataGridViewのDataSourceにDataTableを設定する
DataGridViewにDataTableを表示するには、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に反映する
DataGridViewにDataTableをバインドしている場合、セルを編集すると基本的に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}%'";
フィルタを解除するには、Filterにnullを設定します。
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の内容をデータベースに更新する
SqlDataAdapterとSqlCommandBuilderを使うと、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に頼りすぎず、InsertCommand、UpdateCommand、DeleteCommandを明示的に設定することも多いです。
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側で絞り込みや並び替えを行う方が効率的です。
SQLSELECT Id, Name, Department, Age
FROM Employees
WHERE Department = @Department
ORDER BY Age DESC
9-5. 大量データを扱うときの注意点
DataTableはメモリ上にデータを保持するため、大量データを詰め込みすぎるとメモリ使用量が増えます。
大量データを扱う場合は、次の点に注意します。
必要な列だけ取得する
必要な行だけ取得する
SQL側で
WHEREやORDER BYを使うページングを検討する
DataTableにすべて読み込まない表示件数を制限する
集計は可能ならDB側で行う
悪い例です。
SQLSELECT * FROM Employees
必要な列だけ取得する例です。
SQLSELECT Id, Name, Department, Age
FROM Employees
WHERE Department = @Department
ORDER BY Name
ページングの例です。
SQLSELECT 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に表示されない
DataGridViewにDataTableが表示されない場合は、以下を確認します。
まず、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>を使い分ける
DataTableとList<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に変換できる?
できます。よくある方法は、DataTableをList<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");
検索にはSelectやFindを使えます。
C#DataRow[] rows = table.Select("Age >= 30");
DataRow row = table.Rows.Find(1);
並び替えにはSelect、DataView、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では、DataGridViewのDataSourceに設定するだけで一覧表示できます。
C#dataGridView1.DataSource = table;
DataTableは便利な一方で、列名を文字列で扱うためタイプミスに弱く、DBNullや型変換にも注意が必要です。保守性を高めるには、列名の定数化、Field<T>()の活用、NULLチェック、List<T>との使い分けを意識しましょう。
C#でデータベース結果や一覧画面、CSV・Excelのような表形式データを扱う場合、DataTableを正しく理解しておくと、実務で非常に役立ちます。

