C# DataTableの使い方完全ガイド|作成・追加・検索・更新・削除をサンプルで解説
はじめに
C#で表形式のデータを扱うときに便利なのが、DataTableクラスです。データベースから取得した結果を一時的に保持したり、複数のデータを検索・並べ替え・集計したりする場面で広く利用されています。
DataTableには、列の定義、行の追加、条件検索、更新、削除といった、データベースのテーブルに近い操作が用意されています。一方で、値の取得方法やNULLの扱い、Selectメソッドの条件式など、初めて使う人が迷いやすいポイントも少なくありません。
この記事では、C#のDataTableについて、作成からデータの追加、取得、検索、並べ替え、更新までをサンプルコード付きで解説します。
コード例では、主に次の名前空間を使用します。
using System;using System.Data;using System.Linq;1. C#のDataTableとは
1-1. DataTableの基本概念と主な用途
DataTableは、メモリ上で表形式のデータを管理するためのクラスです。データベースのテーブルと同じように、列と行でデータを表現します。
たとえば、社員情報をDataTableで管理する場合、次のような構造になります。
| Id | Name | Department | Age |
|---|---|---|---|
| 1 | 佐藤 | 営業部 | 28 |
| 2 | 鈴木 | 開発部 | 35 |
| 3 | 高橋 | 総務部 | 42 |
DataTableでは、列をDataColumn、行をDataRowとして扱います。
主な用途は次のとおりです。
データベースから取得した検索結果の保持
CSVやExcelから読み込んだデータの一時保存
表形式データの検索、並べ替え、集計
Windows FormsやWPFの画面へのデータ表示
複数のデータソース間でのデータ受け渡し
バッチ処理や帳票作成用データの加工
DataTableはデータベースそのものではなく、アプリケーションのメモリ上に作成されるオブジェクトです。そのため、DataTableの内容を変更しても、自動的にデータベースへ反映されるわけではありません。
1-2. DataTable・DataSet・DataViewの違い
DataTableと一緒に使われることが多いクラスとして、DataSetとDataViewがあります。
それぞれの役割は次のとおりです。
| クラス | 役割 |
| DataTable | 1つの表形式データを保持する |
| DataSet | 複数のDataTableとテーブル間の関連を管理する |
| DataView | DataTableのデータを絞り込み、並べ替えて表示する |
DataSetには複数のDataTableを格納できます。
DataSet dataSet = new DataSet();DataTable employeeTable = new DataTable("Employees");DataTable departmentTable = new DataTable("Departments");
dataSet.Tables.Add(employeeTable);dataSet.Tables.Add(departmentTable);
一方、DataViewは既存のDataTableを直接変更せず、表示対象の行や順序を切り替えるために使います。
DataView view = new DataView(employeeTable);view.RowFilter = "Age >= 30";view.Sort = "Age DESC";1つの表を扱うだけならDataTable、複数の表をまとめて扱うならDataSet、表示用に絞り込みや並べ替えを行うならDataViewが適しています。
1-3. Listや配列との違い
DataTableとList<T>、配列には、データ構造や型の扱いに違いがあります。
配列は、同じ型の値を固定長で保持するのに適しています。
string[] names = { "佐藤", "鈴木", "高橋" };List<T>は、同じ型のオブジェクトを可変長で管理するのに適しています。
List<string> names = new List<string>{"佐藤","鈴木","高橋"};複数の項目を持つデータをListで管理する場合は、専用クラスを定義するのが一般的です。
public class Employee{public int Id { get; set; }public string Name { get; set; } = string.Empty;public int Age { get; set; }}List<Employee> employees = new List<Employee>{new Employee { Id = 1, Name = "佐藤", Age = 28 },new Employee { Id = 2, Name = "鈴木", Age = 35 }};DataTableは、実行時に列構成を変更できることや、文字列の条件式による検索、DataViewとの連携ができる点が特徴です。
| 比較項目 | DataTable | List<T> | 配列 |
| 表形式データ | 得意 | クラス定義が必要 | あまり向かない |
| 型安全性 | 比較的低い | 高い | 高い |
| 列の動的な追加 | 可能 | 原則として不可 | 不可 |
| 条件検索 | SelectやLINQ | LINQ | LINQ |
| データバインド | 対応しやすい | 対応可能 | 用途が限定的 |
| データベースとの連携 | 得意 | 変換が必要な場合がある | 不向き |
アプリケーション内でデータ構造が決まっている場合は、型安全なList<T>のほうが扱いやすいことがあります。データベースの検索結果や列構成が動的に変化するデータには、DataTableが適しています。
1-4. DataTableが適しているケース
DataTableは、次のようなケースに適しています。
まず、データベースから取得した結果をそのまま表形式で保持したいケースです。SqlDataAdapterなどを使うと、検索結果をDataTableへ格納できます。
DataTable table = new DataTable();// SqlDataAdapter adapter = new SqlDataAdapter(sql, connection);// adapter.Fill(table);
また、列構成が実行時まで確定しないケースにも向いています。DataTableでは、プログラムの実行中に列を追加できます。
DataTable table = new DataTable();table.Columns.Add("Column1", typeof(string));table.Columns.Add("Column2", typeof(int));
そのほか、CSVデータの加工、帳票用データの生成、複数条件による絞り込み、データバインドなどにも便利です。
ただし、大量データをすべてDataTableへ読み込むと、メモリ使用量が増加します。数十万件以上のデータを扱う場合は、データベース側で必要な行だけを絞り込む、DataReaderで順次処理するなどの方法も検討しましょう。
1-5. DataTableを使うための準備
DataTableを利用するには、System.Data名前空間を読み込みます。
using System.Data;LINQやField<T>メソッドを利用する場合は、System.Linqも読み込みます。
using System.Linq;.NETの一般的なコンソールアプリケーションでは、そのままDataTableを利用できます。
最小構成のコードは次のとおりです。
using System;using System.Data;class Program{static void Main(){DataTable table = new DataTable();
Console.WriteLine("DataTableを作成しました。");}
}
2. C#でDataTableを作成する方法
2-1. DataTableを初期化する
DataTableは、new DataTable()で初期化します。
DataTable table = new DataTable();テーブル名を指定して初期化することもできます。
DataTable table = new DataTable("Employees");テーブル名は、DataSetに複数のDataTableを格納するときや、デバッグ時にテーブルを識別するときに役立ちます。
Console.WriteLine(table.TableName);実行結果は次のとおりです。
Employees2-2. Columns.Addで列を追加する
DataTableを作成した直後は、列が存在しません。Columns.Addメソッドを使って、必要な列を追加します。
DataTable table = new DataTable("Employees");table.Columns.Add("Id");table.Columns.Add("Name");table.Columns.Add("Department");
データ型を省略すると、列の型は基本的にstringになります。
列数はColumns.Countで確認できます。
Console.WriteLine(table.Columns.Count);実行結果は次のとおりです。
3列を取得するときは、列番号または列名を指定します。
DataColumn firstColumn = table.Columns[0];DataColumn nameColumn = table.Columns["Name"];Console.WriteLine(firstColumn.ColumnName);Console.WriteLine(nameColumn.DataType);
2-3. 列名とデータ型を指定する
DataTableの列には、列名とデータ型を指定できます。
DataTable table = new DataTable("Employees");table.Columns.Add("Id", typeof(int));table.Columns.Add("Name", typeof(string));table.Columns.Add("Age", typeof(int));table.Columns.Add("HireDate", typeof(DateTime));table.Columns.Add("IsActive", typeof(bool));
よく使用するデータ型は次のとおりです。
| C#の型 | 用途 |
string | 名前、住所、コードなどの文字列 |
int | ID、年齢、数量などの整数 |
decimal | 金額や精度が必要な小数 |
double | 計算用の浮動小数点数 |
DateTime | 日付と時刻 |
bool | trueまたはfalseの状態 |
列の型と一致しない値を追加すると、変換できない場合に例外が発生します。
table.Rows.Add(1, "佐藤", 28, new DateTime(2022, 4, 1), true);int型の列に数値へ変換できない文字列を追加すると、エラーになります。
// Age列がint型なので例外が発生するtable.Rows.Add(2, "鈴木", "年齢不明", DateTime.Today, true);2-4. 主キーを設定する
主キーとは、各行を一意に識別するための列です。DataTableでは、PrimaryKeyプロパティにDataColumnの配列を設定します。
DataTable table = new DataTable("Employees");table.Columns.Add("Id", typeof(int));table.Columns.Add("Name", typeof(string));
table.PrimaryKey = new[]{table.Columns["Id"]};
主キーを設定すると、同じ値を持つ行を複数追加できなくなります。
table.Rows.Add(1, "佐藤");// 同じIdなので例外が発生する// table.Rows.Add(1, "鈴木");
また、主キーを設定しておくと、Rows.Findメソッドで高速に行を検索できます。
DataRow? row = table.Rows.Find(1);if (row != null){Console.WriteLine(row["Name"]);}
複合主キーを設定する場合は、複数の列を指定します。
table.PrimaryKey = new[]{table.Columns["DepartmentId"],table.Columns["EmployeeNumber"]};複合主キーを検索する場合は、キー値を配列で渡します。
DataRow? row = table.Rows.Find(new object[] { 10, 1001 });2-5. NULL・既定値・一意制約を設定する
DataColumnには、NULLの許可、既定値、一意制約などを設定できます。
NULLを禁止する場合は、AllowDBNullをfalseにします。
DataColumn nameColumn = table.Columns.Add("Name", typeof(string));nameColumn.AllowDBNull = false;値が指定されなかったときの既定値は、DefaultValueで設定します。
DataColumn isActiveColumn = table.Columns.Add("IsActive", typeof(bool));isActiveColumn.DefaultValue = true;列の値を一意にする場合は、Uniqueをtrueにします。
DataColumn emailColumn = table.Columns.Add("Email", typeof(string));emailColumn.Unique = true;IDを自動採番する場合は、AutoIncrementを利用できます。
DataColumn idColumn = table.Columns.Add("Id", typeof(int));idColumn.AutoIncrement = true;idColumn.AutoIncrementSeed = 1;idColumn.AutoIncrementStep = 1;
設定後は、Idを指定せずに行を追加できます。
table.Rows.Add(null, "佐藤");table.Rows.Add(null, "鈴木");制約を設定することで、不正なデータがDataTableへ追加されるのを防げます。
2-6. サンプルで使用するDataTableを作成する
ここからのサンプルでは、次のメソッドで作成したDataTableを使用します。
static DataTable CreateEmployeeTable(){DataTable table = new DataTable("Employees");DataColumn idColumn = table.Columns.Add("Id", typeof(int));idColumn.AllowDBNull = false;idColumn.Unique = true;DataColumn nameColumn = table.Columns.Add("Name", typeof(string));nameColumn.AllowDBNull = false;DataColumn departmentColumn =table.Columns.Add("Department", typeof(string));departmentColumn.AllowDBNull = true;DataColumn ageColumn = table.Columns.Add("Age", typeof(int));ageColumn.AllowDBNull = false;DataColumn salaryColumn =table.Columns.Add("Salary", typeof(decimal));salaryColumn.AllowDBNull = false;salaryColumn.DefaultValue = 0m;DataColumn hireDateColumn =table.Columns.Add("HireDate", typeof(DateTime));hireDateColumn.AllowDBNull = false;DataColumn isActiveColumn =table.Columns.Add("IsActive", typeof(bool));isActiveColumn.AllowDBNull = false;isActiveColumn.DefaultValue = true;table.PrimaryKey = new[] { idColumn };return table;
}
サンプルデータを追加するメソッドも用意します。
static DataTable CreateSampleData(){DataTable table = CreateEmployeeTable();table.Rows.Add(1,"佐藤","営業部",28,320000m,new DateTime(2022, 4, 1),true);table.Rows.Add(2,"鈴木","開発部",35,450000m,new DateTime(2018, 10, 15),true);table.Rows.Add(3,"高橋","総務部",42,500000m,new DateTime(2015, 6, 1),false);table.Rows.Add(4,"田中","開発部",31,410000m,new DateTime(2020, 1, 10),true);table.Rows.Add(5,"伊藤",DBNull.Value,26,290000m,new DateTime(2024, 4, 1),true);return table;
}
3. DataTableにデータを追加する方法
3-1. Rows.Addで行を直接追加する
最も簡単な追加方法は、Rows.Addメソッドへ各列の値を渡す方法です。
DataTable table = CreateEmployeeTable();table.Rows.Add(1,"佐藤","営業部",28,320000m,new DateTime(2022, 4, 1),true);
値は、列が定義されている順番で指定します。
列の順番は次のとおりです。
IdNameDepartmentAgeSalaryHireDateIsActive値の数や順番を間違えると、意図しない列に値が格納されたり、型変換エラーが発生したりします。
列の値を配列として渡すこともできます。
object[] values ={2,"鈴木","開発部",35,450000m,new DateTime(2018, 10, 15),true};table.Rows.Add(values);
3-2. NewRowでDataRowを作成して追加する
NewRowメソッドを使うと、新しいDataRowを作成してから値を設定できます。
DataTable table = CreateEmployeeTable();DataRow row = table.NewRow();
row["Id"] = 1;row["Name"] = "佐藤";row["Department"] = "営業部";row["Age"] = 28;row["Salary"] = 320000m;row["HireDate"] = new DateTime(2022, 4, 1);row["IsActive"] = true;
table.Rows.Add(row);
NewRowを使う方法は、列名を見ながら値を設定できるため、列数が多い場合に読みやすくなります。
値を設定しただけでは、DataTableに行は追加されません。最後に必ずRows.Addを実行します。
DataRow row = table.NewRow();row["Id"] = 1;// この時点ではDataTableに追加されていないtable.Rows.Add(row);
3-3. 複数行のデータを追加する
複数行を追加するときは、繰り返し処理を使用できます。
DataTable table = CreateEmployeeTable();var employees = new[]{new{Id = 1,Name = "佐藤",Department = "営業部",Age = 28,Salary = 320000m,HireDate = new DateTime(2022, 4, 1),IsActive = true},new{Id = 2,Name = "鈴木",Department = "開発部",Age = 35,Salary = 450000m,HireDate = new DateTime(2018, 10, 15),IsActive = true}};
foreach (var employee in employees){table.Rows.Add(employee.Id,employee.Name,employee.Department,employee.Age,employee.Salary,employee.HireDate,employee.IsActive);}
大量の行を追加する場合は、BeginLoadDataとEndLoadDataを利用すると、制約チェックなどの処理を一時的に抑えられる場合があります。
table.BeginLoadData();try{for (int i = 1; i <= 10000; i++){table.Rows.Add(i,$"社員{i}","開発部",30,300000m,DateTime.Today,true);}}finally{table.EndLoadData();}
EndLoadDataでは制約の検証が行われるため、主キーの重複などがあると例外が発生する可能性があります。
3-4. DBNull.Valueを使ってNULLを追加する
DataTableでデータベースのNULLに相当する値を設定するときは、C#のnullではなくDBNull.Valueを使用します。
DataTable table = CreateEmployeeTable();table.Rows.Add(1,"佐藤",DBNull.Value,28,320000m,new DateTime(2022, 4, 1),true);
NewRowを使う場合も同様です。
DataRow row = table.NewRow();row["Id"] = 2;row["Name"] = "鈴木";row["Department"] = DBNull.Value;row["Age"] = 35;row["Salary"] = 450000m;row["HireDate"] = new DateTime(2018, 10, 15);row["IsActive"] = true;
table.Rows.Add(row);
文字列が空の場合にNULLを設定したいときは、条件演算子を利用できます。
string? department = null;row["Department"] =string.IsNullOrWhiteSpace(department)? DBNull.Value: department;
ただし、対象列のAllowDBNullがfalseの場合は、DBNull.Valueを設定できません。
3-5. 別のDataTableからデータを追加する
別のDataTableから行を追加する方法はいくつかあります。
列構成が同じ場合は、ImportRowを利用できます。
DataTable sourceTable = CreateSampleData();DataTable destinationTable = sourceTable.Clone();foreach (DataRow sourceRow in sourceTable.Rows){destinationTable.ImportRow(sourceRow);}
Cloneは、列や制約などの構造だけをコピーします。行データはコピーされません。
DataTable structureOnly = sourceTable.Clone();構造とデータの両方をコピーしたい場合は、Copyを使います。
DataTable copiedTable = sourceTable.Copy();複数のDataTableを結合する場合は、Mergeも利用できます。
DataTable table1 = CreateSampleData();DataTable table2 = CreateEmployeeTable();table2.Rows.Add(6,"山本","営業部",33,380000m,new DateTime(2019, 8, 1),true);
table1.Merge(table2);
主キーが設定されている場合、同じ主キーを持つ行は既存行と照合されます。単純に全行を追加したい場合は、主キーの重複に注意してください。
3-6. データ追加時の型変換エラーを防ぐ
DataTableの列にはデータ型が設定されているため、変換できない値を追加すると例外が発生します。
たとえば、Age列がint型であるにもかかわらず、数値に変換できない文字列を設定するとエラーになります。
DataRow row = table.NewRow();// FormatExceptionなどの原因になるrow["Age"] = "三十歳";
外部から受け取った文字列を追加する場合は、TryParseで検証します。
string inputAge = "30";if (int.TryParse(inputAge, out int age)){row["Age"] = age;}else{Console.WriteLine("年齢の形式が正しくありません。");}
日付も同様です。
string inputDate = "2024-04-01";if (DateTime.TryParse(inputDate, out DateTime hireDate)){row["HireDate"] = hireDate;}else{Console.WriteLine("入社日の形式が正しくありません。");}
金額にはdecimal.TryParseを使用します。
string inputSalary = "350000";if (decimal.TryParse(inputSalary, out decimal salary)){row["Salary"] = salary;}
列が存在するか確認してから値を設定すると、列名の間違いによる例外を防げます。
if (table.Columns.Contains("Age")){row["Age"] = 30;}また、外部入力をDataTableへ追加するときは、次の項目を確認しましょう。
必須値が入力されているか
列のデータ型へ変換できるか
主キーや一意制約に違反しないか
NULLを許可している列か
文字列の長さが制限を超えていないか
4. DataTableのデータを取得・表示する方法
4-1. RowsとColumnsを指定して値を取得する
DataTableの値は、行番号と列番号を指定して取得できます。
DataTable table = CreateSampleData();object value = table.Rows[0][1];
Console.WriteLine(value);
実行結果は次のとおりです。
佐藤Rows[0]は1行目、[1]は2列目を表します。インデックスは0から始まります。
列番号ではなく、DataColumnを指定することもできます。
DataColumn nameColumn = table.Columns["Name"];object name = table.Rows[0][nameColumn];Console.WriteLine(name);
値を特定の型として使用するときは、キャストまたは変換が必要です。
int id = (int)table.Rows[0]["Id"];string name = (string)table.Rows[0]["Name"];値がDBNullの可能性がある列を直接キャストすると、例外が発生するため注意が必要です。
4-2. foreachで全行を順番に取得する
全行を順番に処理する場合は、foreachを使用します。
DataTable table = CreateSampleData();foreach (DataRow row in table.Rows){Console.WriteLine($"{row["Id"]}: {row["Name"]}");}
実行結果の例は次のとおりです。
1: 佐藤2: 鈴木3: 高橋4: 田中5: 伊藤すべての列を動的に取得する場合は、DataColumnを繰り返し処理します。
foreach (DataRow row in table.Rows){foreach (DataColumn column in table.Columns){Console.Write($"{column.ColumnName}={row[column]} ");}Console.WriteLine();
}
DataTableの列構成が実行時に変化する場合でも、この方法なら全項目を表示できます。
4-3. 列名を指定してデータを取得する
列名を指定すると、コードを見ただけで何の値を取得しているか分かりやすくなります。
DataRow row = table.Rows[0];int id = Convert.ToInt32(row["Id"]);string name = Convert.ToString(row["Name"]) ?? string.Empty;int age = Convert.ToInt32(row["Age"]);
Console.WriteLine($"{id}, {name}, {age}");
列名が存在しない場合、ArgumentExceptionが発生します。動的なDataTableを扱うときは、Columns.Containsで確認できます。
if (table.Columns.Contains("Department")){Console.WriteLine(row["Department"]);}列名の大文字と小文字の扱いは、DataTableの設定や列構成によって問題になる場合があります。コード内では、定義した列名と同じ表記を使用するのが安全です。
列名を定数化しておくと、入力ミスを減らせます。
const string IdColumn = "Id";const string NameColumn = "Name";int id = Convert.ToInt32(row[IdColumn]);string name = Convert.ToString(row[NameColumn]) ?? string.Empty;
4-4. Fieldメソッドで型安全に値を取得する
Field<T>メソッドを使うと、列の値を指定した型で取得できます。
DataRow row = table.Rows[0];int id = row.Field<int>("Id");string? name = row.Field<string>("Name");decimal salary = row.Field<decimal>("Salary");DateTime hireDate = row.Field<DateTime>("HireDate");
Field<T>はLINQでDataTableを操作するときにもよく使用します。
var names = table.AsEnumerable().Select(row => row.Field<string>("Name")).ToList();NULLを許可する値型の列では、Nullable型を指定します。
int? age = row.Field<int?>("Age");DateTime? date = row.Field<DateTime?>("HireDate");DBNullが格納された列を、NULL非許容の型で取得すると例外になります。
// DepartmentがDBNullの場合でもstring?ならnullとして取得できるstring? department = row.Field<string?>("Department");Field<T>は値を別の型へ自動変換するメソッドではありません。たとえば、列の型がintである場合にField<string>で取得することはできません。
int age = row.Field<int>("Age");string ageText = age.ToString();4-5. NULLを判定して安全に値を取得する
DataTableのNULLはDBNull.Valueで表現されます。NULLを判定するには、IsNullメソッドが便利です。
DataRow row = table.Rows[4];if (row.IsNull("Department")){Console.WriteLine("部署は未設定です。");}else{Console.WriteLine(row["Department"]);}
DBNull.Valueと比較する方法もあります。
if (row["Department"] == DBNull.Value){Console.WriteLine("部署は未設定です。");}Field<T>を使用する場合は、NULL許容型として取得できます。
string? department = row.Field<string?>("Department");Console.WriteLine(department ?? "未設定");
値型の場合はNullable型を使います。
int? age = row.Field<int?>("Age");if (age.HasValue){Console.WriteLine(age.Value);}else{Console.WriteLine("年齢は未設定です。");}
Convert.ToStringを使うと、DBNull.Valueは空文字列になります。
string department =Convert.ToString(row["Department"]) ?? string.Empty;ただし、この方法では「NULL」と「もともとの空文字列」を区別できません。両者を区別する必要がある場合は、先にIsNullで確認しましょう。
4-6. 行数と列数を取得する
DataTableの行数はRows.Count、列数はColumns.Countで取得できます。
DataTable table = CreateSampleData();int rowCount = table.Rows.Count;int columnCount = table.Columns.Count;
Console.WriteLine($"行数: {rowCount}");Console.WriteLine($"列数: {columnCount}");
実行結果は次のとおりです。
行数: 5列数: 7データが存在するか確認する場合は、Rows.Countが0より大きいか判定します。
if (table.Rows.Count > 0){Console.WriteLine("データがあります。");}else{Console.WriteLine("データがありません。");}列が存在するか確認するときは、Columns.Containsを使用できます。
if (table.Columns.Contains("Name")){Console.WriteLine("Name列があります。");}5. DataTableのデータを検索する方法
5-1. Selectメソッドで条件に一致する行を検索する
DataTableのSelectメソッドを使うと、条件に一致するDataRowの配列を取得できます。
DataTable table = CreateSampleData();DataRow[] rows = table.Select("Department = '開発部'");
foreach (DataRow row in rows){Console.WriteLine(row["Name"]);}
実行結果は次のとおりです。
鈴木田中Selectの条件式では、列名、比較演算子、値を文字列で指定します。
DataRow[] rows = table.Select("Age >= 30");文字列はシングルクォートで囲みます。
DataRow[] rows = table.Select("Name = '佐藤'");条件式の列名をユーザー入力から直接組み立てると、不正な式や特殊文字によってエラーになる可能性があります。検索条件に外部入力を使用する場合は、値を適切にエスケープしてください。
5-2. 完全一致・部分一致・範囲指定で検索する
完全一致検索では、=を使用します。
DataRow[] rows = table.Select("Department = '営業部'");不一致検索では、<>を使用します。
DataRow[] rows = table.Select("Department <> '開発部'");数値の範囲を検索する場合は、比較演算子を組み合わせます。
DataRow[] rows =table.Select("Age >= 30 AND Age <= 40");INを使うと、複数の候補から一致する値を検索できます。
DataRow[] rows =table.Select("Department IN ('営業部', '開発部')");文字列の部分一致にはLIKEを使用します。
DataRow[] rows = table.Select("Name LIKE '佐%'");検索条件が固定されている場合はSelectが手軽です。一方、複雑な条件やC#のメソッドを使った判定が必要な場合はLINQが適しています。
5-3. AND・ORを使って複数条件で検索する
複数の条件をすべて満たす行を検索するときは、ANDを使用します。
DataRow[] rows =table.Select("Department = '開発部' AND Age >= 30");いずれかの条件を満たす行を検索するときは、ORを使用します。
DataRow[] rows =table.Select("Department = '営業部' OR Department = '総務部'");ANDとORを組み合わせる場合は、括弧を使用して評価順序を明確にします。
DataRow[] rows = table.Select("(Department = '営業部' OR Department = '開発部')" +" AND Age >= 30");否定条件にはNOTを使用できます。
DataRow[] rows =table.Select("NOT Department = '開発部'");ただし、NULLを含む列では通常の比較だけでは対象にならない場合があります。NULLも含めて条件を作る場合は、IS NULLを明示します。
DataRow[] rows = table.Select("Department <> '開発部' OR Department IS NULL");5-4. LIKEを使って文字列を部分検索する
文字列を部分検索するときは、LIKEを使用します。
前方一致の例です。
DataRow[] rows = table.Select("Name LIKE '佐%'");後方一致の例です。
DataRow[] rows = table.Select("Name LIKE '%木'");文字列を含む行を検索する例です。
DataRow[] rows = table.Select("Name LIKE '%藤%'");検索文字列にシングルクォートが含まれる場合は、シングルクォートを2つに置換します。
string keyword = "O'Neil";string escapedKeyword = keyword.Replace("'", "''");DataRow[] rows =table.Select($"Name LIKE '%{escapedKeyword}%'");
角括弧などの特殊文字もLIKEのパターンとして解釈される可能性があります。複雑な外部入力を検索する場合は、LINQでContainsを使用する方法も分かりやすいでしょう。
string keyword = "藤";var rows = table.AsEnumerable().Where(row =>(row.Field<string>("Name") ?? string.Empty).Contains(keyword));
大文字と小文字の区別は、DataTableのCaseSensitiveプロパティに影響されます。
table.CaseSensitive = true;5-5. 日付や数値を条件にして検索する
数値はクォートで囲まずに指定します。
DataRow[] rows = table.Select("Salary >= 400000");範囲検索も可能です。
DataRow[] rows =table.Select("Salary >= 300000 AND Salary < 450000");日付条件は、文字列を直接組み立てるより、LINQでDateTimeとして比較する方法が安全です。
DateTime targetDate = new DateTime(2020, 1, 1);var rows = table.AsEnumerable().Where(row =>row.Field<DateTime>("HireDate") >= targetDate);
終了日を含む範囲検索の例です。
DateTime startDate = new DateTime(2018, 1, 1);DateTime endDate = new DateTime(2022, 12, 31);var rows = table.AsEnumerable().Where(row =>{DateTime hireDate = row.Field<DateTime>("HireDate");
return hireDate >= startDate &&hireDate <= endDate;});</span></code></pre><p><span>時刻を含むDateTimeを日単位で検索する場合は、翌日の直前までを指定するより、「開始日時以上、翌日未満」で判定すると確実です。</span></p><pre dir="ltr"><code dir="ltr"><span>DateTime targetDay = new DateTime(2022, 4, 1);
DateTime nextDay = targetDay.AddDays(1);
var rows = table.AsEnumerable().Where(row =>{DateTime hireDate = row.Field<DateTime>("HireDate");
return hireDate >= targetDay &&hireDate < nextDay;});</span></code></pre><h3><span>5-6. IS NULLでNULLを含む行を検索する</span></h3><p><span>NULLを含む行を検索する場合は、</span><code dir="ltr"><span>IS NULL</span></code><span>を使用します。</span></p><pre dir="ltr"><code dir="ltr"><span>DataRow[] rows =table.Select("Department IS NULL");</span></code></pre><p><span>NULLではない行を検索する場合は、</span><code dir="ltr"><span>IS NOT NULL</span></code><span>を使用します。</span></p><pre dir="ltr"><code dir="ltr"><span>DataRow[] rows =table.Select("Department IS NOT NULL");</span></code></pre><p><span>LINQでNULLを検索する場合は、</span><code dir="ltr"><span>Field<T></span></code><span>でnullとして取得できます。</span></p><pre dir="ltr"><code dir="ltr"><span>var rows = table.AsEnumerable().Where(row =>row.Field<string?>("Department") == null);</span></code></pre><p><code dir="ltr"><span>IsNull</span></code><span>メソッドを使う方法もあります。</span></p><pre dir="ltr"><code dir="ltr"><span>var rows = table.AsEnumerable().Where(row => row.IsNull("Department"));</span></code></pre><p><span>NULLと空文字列は異なる値です。NULLと空文字列の両方を未設定として扱う場合は、条件を組み合わせます。</span></p><pre dir="ltr"><code dir="ltr"><span>DataRow[] rows = table.Select("Department IS NULL OR Department = ''");</span></code></pre><p><span>LINQでは次のように記述できます。</span></p><pre dir="ltr"><code dir="ltr"><span>var rows = table.AsEnumerable().Where(row =>string.IsNullOrWhiteSpace(row.Field<string?>("Department")));</span></code></pre><h3><span>5-7. Rows.Findで主キーから1行を検索する</span></h3><p><span>主キーが設定されているDataTableでは、</span><code dir="ltr"><span>Rows.Find</span></code><span>を使って1行を検索できます。</span></p><pre dir="ltr"><code dir="ltr"><span>DataTable table = CreateSampleData();
DataRow? row = table.Rows.Find(2);
if (row != null){Console.WriteLine(row["Name"]);}else{Console.WriteLine("対象データがありません。");}
実行結果は次のとおりです。
鈴木Rows.Findを利用するには、あらかじめ主キーを設定しておく必要があります。
table.PrimaryKey = new[]{table.Columns["Id"]};主キーが設定されていない状態でRows.Findを実行すると、例外が発生します。
複合主キーの場合は、キー値をobject配列で指定します。
DataRow? row =table.Rows.Find(new object[] { 10, 1001 });主キーが分かっている場合は、条件式を解析するSelectよりRows.Findのほうが、検索の意図が明確です。
5-8. LINQでDataTableを検索する
AsEnumerableを使うと、DataTableに対してLINQを使用できます。
DataTable table = CreateSampleData();var rows = table.AsEnumerable().Where(row =>row.Field<int>("Age") >= 30);
結果を表示します。
foreach (DataRow row in rows){Console.WriteLine($"{row.Field<string>("Name")} " +$"{row.Field<int>("Age")}歳");}複数条件もC#の論理演算子で記述できます。
var rows = table.AsEnumerable().Where(row =>row.Field<string?>("Department") == "開発部" &&row.Field<int>("Age") >= 30);部分一致検索では、Containsを利用できます。
string keyword = "藤";var rows = table.AsEnumerable().Where(row =>(row.Field<string>("Name") ?? string.Empty).Contains(keyword));
大文字と小文字を区別せずに検索する場合は、StringComparisonを指定します。
string keyword = "sato";var rows = table.AsEnumerable().Where(row =>(row.Field<string>("Name") ?? string.Empty).Contains(keyword,StringComparison.OrdinalIgnoreCase));
LINQは型を利用して条件を記述できるため、文字列条件式の記述ミスを減らしやすい方法です。
5-9. 検索結果を新しいDataTableに変換する
LINQの検索結果を新しいDataTableへ変換するには、CopyToDataTableを使用します。
var rows = table.AsEnumerable().Where(row =>row.Field<int>("Age") >= 30);DataTable resultTable = rows.CopyToDataTable();
Selectの検索結果も、LINQへ変換してからCopyToDataTableを使用できます。
DataRow[] rows =table.Select("Department = '開発部'");DataTable resultTable =rows.CopyToDataTable();
元のDataTableと同じ構造を持つ空のDataTableを作成し、検索結果をImportRowで追加する方法もあります。
DataTable resultTable = table.Clone();foreach (DataRow row in rows){resultTable.ImportRow(row);}
必要な列だけを持つ新しいDataTableを作る場合は、明示的に列を定義します。
DataTable resultTable = new DataTable();resultTable.Columns.Add("Name", typeof(string));resultTable.Columns.Add("Salary", typeof(decimal));
foreach (DataRow row in rows){resultTable.Rows.Add(row.Field<string>("Name"),row.Field<decimal>("Salary"));}
5-10. 検索結果が0件の場合のCopyToDataTableエラーを防ぐ
CopyToDataTableは、検索結果が0件の場合にInvalidOperationExceptionを発生させます。
次のコードは、該当する行がないとエラーになります。
var rows = table.AsEnumerable().Where(row =>row.Field<int>("Age") >= 100);// 0件の場合は例外が発生するDataTable resultTable = rows.CopyToDataTable();
0件の場合を考慮するには、Anyで確認します。
var rows = table.AsEnumerable().Where(row =>row.Field<int>("Age") >= 100);DataTable resultTable;
if (rows.Any()){resultTable = rows.CopyToDataTable();}else{resultTable = table.Clone();}
ただし、列挙処理を複数回実行したくない場合は、先にListへ変換できます。
List<DataRow> rows = table.AsEnumerable().Where(row =>row.Field<int>("Age") >= 100).ToList();DataTable resultTable = rows.Count > 0? rows.CopyToDataTable(): table.Clone();
Selectの結果を使う場合は、配列の長さを確認します。
DataRow[] rows =table.Select("Age >= 100");DataTable resultTable = rows.Length > 0? rows.CopyToDataTable(): table.Clone();
検索結果が0件でも、元のDataTableと同じ列構成を持つ空のDataTableを返すようにすると、呼び出し側の処理を統一できます。
6. DataTableのデータを並べ替え・絞り込みする方法
6-1. Selectメソッドで検索結果を並べ替える
Selectメソッドの第2引数には、並べ替え条件を指定できます。
DataRow[] rows =table.Select("Age >= 30","Age ASC");ASCは昇順、DESCは降順です。
DataRow[] rows =table.Select("Age >= 30","Salary DESC");複数列で並べ替えることもできます。
DataRow[] rows =table.Select(string.Empty,"Department ASC, Age DESC");条件を指定せず、すべての行を並べ替える場合は、空文字列を指定します。
DataRow[] rows =table.Select("","HireDate ASC");結果はDataRow配列であり、元のDataTableの行順は変更されません。
6-2. DataViewのRowFilterでデータを絞り込む
DataViewのRowFilterを使うと、DataTableの表示対象を絞り込めます。
DataView view = new DataView(table);view.RowFilter ="Department = '開発部'";
絞り込んだ結果を取得します。
foreach (DataRowView rowView in view){Console.WriteLine(rowView["Name"]);}DataTableのDefaultViewを使用することもできます。
DataView view = table.DefaultView;view.RowFilter = "Age >= 30";フィルターを解除するときは、空文字列を設定します。
view.RowFilter = string.Empty;DataViewの内容をDataTableへ変換するには、ToTableを使用します。
DataTable filteredTable = view.ToTable();RowFilterは元のDataTableから行を削除する処理ではありません。DataView上で表示される行だけが変化します。
6-3. DataViewのSortで昇順・降順に並べ替える
DataViewのSortプロパティを使うと、表示順を変更できます。
年齢の昇順で並べ替える例です。
DataView view = new DataView(table);view.Sort = "Age ASC";
給与の降順で並べ替える例です。
view.Sort = "Salary DESC";複数列を指定することもできます。
view.Sort ="Department ASC, Salary DESC";絞り込みと並べ替えを同時に使用できます。
DataView view = new DataView(table);view.RowFilter ="Department = '開発部'";
view.Sort ="Salary DESC";
結果を新しいDataTableへ変換します。
DataTable resultTable = view.ToTable();DataViewは、画面上の一覧にフィルター機能や並べ替え機能を実装するときに便利です。
6-4. LINQのOrderByで並べ替える
LINQでは、OrderByで昇順、OrderByDescendingで降順に並べ替えます。
var rows = table.AsEnumerable().OrderBy(row =>row.Field<int>("Age"));降順にする場合は次のように記述します。
var rows = table.AsEnumerable().OrderByDescending(row =>row.Field<decimal>("Salary"));複数列で並べ替える場合は、ThenByまたはThenByDescendingを使用します。
var rows = table.AsEnumerable().OrderBy(row =>row.Field<string?>("Department")).ThenByDescending(row =>row.Field<decimal>("Salary"));検索と並べ替えを組み合わせることもできます。
var rows = table.AsEnumerable().Where(row =>row.Field<bool>("IsActive")).OrderByDescending(row =>row.Field<decimal>("Salary"));新しいDataTableへ変換する場合は、0件を考慮します。
List<DataRow> rows = table.AsEnumerable().OrderBy(row =>row.Field<int>("Age")).ToList();DataTable sortedTable = rows.Count > 0? rows.CopyToDataTable(): table.Clone();
6-5. 重複しない値を抽出する
特定の列から重複しない値を抽出する場合は、LINQのDistinctを使用します。
List<string> departments = table.AsEnumerable().Select(row =>row.Field<string?>("Department")).Where(department =>!string.IsNullOrEmpty(department)).Select(department => department!).Distinct().ToList();結果を表示します。
foreach (string department in departments){Console.WriteLine(department);}複数列の組み合わせを重複なしで取得する場合は、匿名型へ変換してDistinctを使用できます。
var values = table.AsEnumerable().Select(row => new{Department =row.Field<string?>("Department"),IsActive =row.Field<bool>("IsActive")}).Distinct().ToList();DataViewのToTableでも重複を除外できます。
DataView view = new DataView(table);DataTable distinctTable = view.ToTable(true,"Department");
第1引数のtrueは重複を除外する指定です。それ以降の引数には、抽出する列名を指定します。
複数列を指定する場合は次のように記述します。
DataTable distinctTable = view.ToTable(true,"Department","IsActive");6-6. 条件に一致する件数や合計値を集計する
条件に一致する件数は、LINQのCountで取得できます。
int count = table.AsEnumerable().Count(row =>row.Field<string?>("Department") == "開発部");Console.WriteLine($"開発部の人数: {count}");
給与の合計はSumで取得できます。
decimal totalSalary = table.AsEnumerable().Sum(row =>row.Field<decimal>("Salary"));Console.WriteLine($"給与合計: {totalSalary}");
条件に一致する行だけを集計できます。
decimal developmentTotal = table.AsEnumerable().Where(row =>row.Field<string?>("Department") == "開発部").Sum(row =>row.Field<decimal>("Salary"));平均値はAverageで取得します。
double averageAge = table.AsEnumerable().Average(row =>row.Field<int>("Age"));最大値と最小値は、MaxとMinを使用します。
decimal maxSalary = table.AsEnumerable().Max(row =>row.Field<decimal>("Salary"));decimal minSalary = table.AsEnumerable().Min(row =>row.Field<decimal>("Salary"));
部署ごとに集計する場合は、GroupByを使用します。
var summaries = table.AsEnumerable().GroupBy(row =>row.Field<string?>("Department") ?? "未設定").Select(group => new{Department = group.Key,Count = group.Count(),TotalSalary = group.Sum(row =>row.Field<decimal>("Salary")),AverageAge = group.Average(row =>row.Field<int>("Age"))});foreach (var summary in summaries){Console.WriteLine($"{summary.Department}: " +$"{summary.Count}人、" +$"給与合計{summary.TotalSalary}円、" +$"平均年齢{summary.AverageAge:F1}歳");}DataTableのComputeメソッドを使う方法もあります。
object result = table.Compute("SUM(Salary)","Department = '開発部'");decimal total = result == DBNull.Value? 0m: Convert.ToDecimal(result);
件数を求める例です。
object result = table.Compute("COUNT(Id)","IsActive = true");int count = Convert.ToInt32(result);
Computeは簡潔ですが、集計式や条件を文字列で指定します。複雑な集計では、型を利用できるLINQのほうが保守しやすい場合があります。
7. DataTableのデータを更新する方法
7-1. 行番号を指定して値を更新する
行番号を指定して更新する場合は、RowsからDataRowを取得して値を代入します。
DataTable table = CreateSampleData();table.Rows[0]["Name"] = "佐藤太郎";table.Rows[0]["Age"] = 29;table.Rows[0]["Salary"] = 340000m;
DataRowを変数へ格納すると、複数列の更新が読みやすくなります。
DataRow row = table.Rows[0];row["Name"] = "佐藤太郎";row["Department"] = "企画部";row["Age"] = 29;
行番号が範囲外の場合は例外が発生するため、事前に行数を確認します。
int rowIndex = 0;if (rowIndex >= 0 &&rowIndex < table.Rows.Count){table.Rows[rowIndex]["Name"] ="佐藤太郎";}
行番号はデータの追加や削除、並べ替えによって変化します。特定のデータを更新する場合は、主キーや検索条件を使う方法が安全です。
7-2. 検索条件に一致した行を更新する
Selectで条件に一致する行を検索し、foreachで更新できます。
DataRow[] rows =table.Select("Department = '開発部'");foreach (DataRow row in rows){row["Salary"] =row.Field<decimal>("Salary") + 20000m;}
複数項目を更新することもできます。
DataRow[] rows =table.Select("IsActive = false");foreach (DataRow row in rows){row["Department"] = DBNull.Value;row["Salary"] = 0m;}
LINQで検索した行も、そのまま更新できます。
var rows = table.AsEnumerable().Where(row =>row.Field<int>("Age") >= 40);foreach (DataRow row in rows){row["IsActive"] = false;}
更新前後の値を明確に管理したい場合は、BeginEditとEndEditを利用できます。
DataRow row = table.Rows[0];row.BeginEdit();
try{row["Name"] = "佐藤太郎";row["Salary"] = 350000m;
row.EndEdit();
}catch{row.CancelEdit();throw;}
DataTableは行の状態をRowStateで管理します。
foreach (DataRow row in table.Rows){Console.WriteLine(row.RowState);}主な状態は次のとおりです。
| RowState | 意味 |
| Added | 新しく追加された行 |
| Modified | 値が変更された行 |
| Deleted | 削除された行 |
| Unchanged | 変更されていない行 |
| Detached | DataTableに所属していない行 |
AcceptChangesを実行すると、現在の内容が確定され、行の状態は原則としてUnchangedになります。
table.AcceptChanges();データベースへ変更を反映する処理でDataAdapterを使用する場合、更新前にAcceptChangesを呼ぶと変更状態を判別できなくなることがあります。データベース更新との組み合わせでは、呼び出すタイミングに注意しましょう。
7-3. 主キーで検索した行を更新する
主キーが設定されている場合は、Rows.Findで対象行を検索して更新できます。
DataTable table = CreateSampleData();DataRow? row = table.Rows.Find(2);
if (row != null){row["Name"] = "鈴木一郎";row["Salary"] = 470000m;}else{Console.WriteLine("更新対象の行が見つかりません。");}
更新処理をメソッド化する例です。
static bool UpdateEmployee(DataTable table,int id,string name,string? department,int age,decimal salary){DataRow? row = table.Rows.Find(id);if (row == null){return false;}row<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["Name"]</span> = name;row<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["Department"]</span> =string.IsNullOrWhiteSpace(department)? DBNull.Value: department;row<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["Age"]</span> = age;row<span data-placeholder-token="true" class="text-token-text-primary cursor-text rounded-sm" style="background-color: color-mix(in srgb, var(--theme-user-selection-bg, var(--selection)) 30%, transparent); padding-top: 4px; padding-bottom: 4px;">["Salary"]</span> = salary;return true;
}
呼び出し例は次のとおりです。
bool updated = UpdateEmployee(table,2,"鈴木一郎","開発部",36,480000m);Console.WriteLine(updated? "更新しました。": "対象データがありません。");
DataTableから行を削除する場合は、DataRowのDeleteメソッドを使用できます。
DataRow? row = table.Rows.Find(3);if (row != null){row.Delete();}
Deleteを実行すると、行は削除状態になります。DataAdapterによるデータベース更新では、この削除状態を利用してDELETE処理が行われます。
メモリ上のDataTableから行を直ちに取り除きたい場合は、Rows.Removeも使用できます。
DataRow? row = table.Rows.Find(3);if (row != null){table.Rows.Remove(row);}
行番号を指定して削除する場合は、RemoveAtを使用します。
if (table.Rows.Count > 0){table.Rows.RemoveAt(0);}複数行を削除する場合、foreachでDataRowCollectionを直接列挙しながらRemoveするとコレクションが変更されるため、例外の原因になります。先に対象行を配列へ取得してから削除します。
DataRow[] rows =table.Select("IsActive = false");foreach (DataRow row in rows){row.Delete();}
全行を削除するだけで変更履歴を必要としない場合は、Clearを使用できます。
table.Clear();Delete、Remove、Clearは用途が異なります。データベースへの削除反映が必要な場合は行の削除状態を残すDelete、DataTableから即座に取り除く場合はRemove、全データを空にする場合はClearを使い分けましょう。
まとめ
C#のDataTableは、表形式のデータをメモリ上で柔軟に管理できるクラスです。列の型や主キー、NULL制約を設定することで、データベースのテーブルに近い構造を作成できます。
DataTableを扱う際の基本的なポイントは次のとおりです。
Columns.Addで列名とデータ型を定義するRows.AddまたはNewRowで行を追加するNULLは
nullではなくDBNull.Valueで表現する値の取得には列名や
Field<T>を使用するSelectやLINQで条件に一致する行を検索する主キー検索には
Rows.Findを使用するDataViewで絞り込みや並べ替えを行うSum、Count、GroupByなどで集計する検索したDataRowへ値を代入して更新する
Delete、Remove、Clearを目的に応じて使い分ける
手軽な条件検索にはSelect、型安全で複雑な処理にはLINQ、画面表示用の絞り込みや並べ替えにはDataViewが適しています。
一方、DataTableは列の値をobjectとして扱う場面が多く、型の不一致やDBNullによる例外が発生しやすい点に注意が必要です。列のデータ型を明確に定義し、Field<T>、TryParse、IsNullなどを活用することで、安全で読みやすいコードを作成できます。

