C#のDataTableをLINQで操作する方法|AsEnumerable・Where・Select・CopyToDataTableまで実例で解説

はじめに

C#で業務アプリケーションを開発していると、データベースやCSV、Excelなどから取得したデータをDataTableで扱う場面がよくあります。

DataTableは表形式のデータを柔軟に扱える便利なクラスですが、行の検索、抽出、並び替え、集計などを従来のループ処理だけで書くと、コードが長くなりがちです。

そこで役立つのがLINQです。

LINQを使うと、DataTableの行データに対して、Whereによる条件抽出、Selectによる列の取得、OrderByによる並び替え、GroupByによる集計などを簡潔に記述できます。

この記事では、C#のDataTableをLINQで操作する方法を、AsEnumerableWhereSelectCopyToDataTableを中心に、実務で使いやすいサンプルコード付きで解説します。

1. C#のDataTableをLINQで操作する基本

1-1. DataTableにLINQを使うと何ができるのか

DataTableにLINQを使うと、表形式のデータに対して次のような操作を簡潔に書けます。

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

たとえば、以下のような処理が可能です。

C#
// 条件に一致する行を取得
var adults = table.AsEnumerable()
.Where(row => row.Field<int>("Age") >= 20);

// 必要な列だけ取得
var names = table.AsEnumerable()
.Select(row => row.Field<string>("Name"));

// 部署ごとに集計
var summary = table.AsEnumerable()
.GroupBy(row => row.Field<string>("Department"))
.Select(g => new
{
Department = g.Key,
Count = g.Count()
});

従来のforeachforで1行ずつ判定するよりも、処理の意図が分かりやすくなります。

1-2. DataTable.Rowsに直接LINQできない理由

DataTable.RowsDataRowCollection型です。

DataRowCollectionはそのままではLINQの標準クエリ演算子を使いやすいIEnumerable<DataRow>ではありません。そのため、次のようなコードは期待どおりに書けません。

C#
// これはそのままでは使いにくい
var result = table.Rows.Where(row => ...);

DataTableにLINQを使う場合は、AsEnumerable()を使ってIEnumerable<DataRow>として扱える形に変換します。

C#
var rows = table.AsEnumerable();

これにより、WhereSelectOrderByGroupByなどのLINQメソッドを使えるようになります。

1-3. LINQ操作に必要な参照設定とusing

DataTableにLINQを使うには、主に以下の名前空間を使用します。

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

AsEnumerableField<T>CopyToDataTableを使う場合は、System.Data.DataSetExtensionsが必要です。

.NET Frameworkのプロジェクトでは、参照設定にSystem.Data.DataSetExtensionsが追加されていないと、AsEnumerableが見つからないことがあります。

.NET Core、.NET 5以降、.NET 6、.NET 7、.NET 8などでは通常そのまま使えることが多いですが、プロジェクト構成によっては参照を確認してください。

1-4. AsEnumerableでDataTableをLINQ対象に変換する

DataTableをLINQで操作する基本形は次のとおりです。

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

AsEnumerable()を呼び出すことで、DataTableの各行をDataRowとしてLINQで扱えるようになります。

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

DataTableに対してLINQを使うときは、まずAsEnumerable()を使うと覚えておくとよいでしょう。

2. サンプル用DataTableを準備する

2-1. 解説で使用するDataTableの構成

この記事では、社員情報を持つDataTableを例にします。

列構成は以下のとおりです。

列名内容
Idint社員ID
Namestring氏名
Departmentstring部署
Ageint年齢
Salarydecimal給与
JoinDateDateTime入社日

このDataTableを使って、条件抽出、列選択、集計、並び替え、CopyToDataTableなどを解説します。

2-2. 列の型を指定してDataTableを作成する

まずはDataTableを作成し、列を定義します。

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

DataColumnには型を指定できます。

型を明確にしておくことで、LINQでField<int>Field<decimal>を使うときに扱いやすくなります。

2-3. サンプルデータを追加するコード

次にサンプルデータを追加します。

C#
table.Rows.Add(1, "佐藤", "営業", 28, 320000m, new DateTime(2021, 4, 1));
table.Rows.Add(2, "鈴木", "開発", 35, 450000m, new DateTime(2018, 10, 1));
table.Rows.Add(3, "田中", "開発", 41, 520000m, new DateTime(2015, 6, 15));
table.Rows.Add(4, "高橋", "人事", 30, 380000m, new DateTime(2020, 1, 10));
table.Rows.Add(5, "伊藤", "営業", 25, 300000m, new DateTime(2022, 7, 1));

以降のサンプルでは、このtableを使って解説します。

3. AsEnumerableの使い方

3-1. AsEnumerableとは

AsEnumerableは、DataTableをLINQで扱える形にするための拡張メソッドです。

DataTable自体はそのままではLINQの対象として扱いにくいため、AsEnumerable()を使ってIEnumerable<DataRow>として操作します。

C#
IEnumerable<DataRow> rows = table.AsEnumerable();

この状態にすると、WhereSelectOrderByなどのLINQメソッドを使えるようになります。

3-2. AsEnumerableの基本構文

基本構文は次のとおりです。

C#
var query = table.AsEnumerable()
.Where(row => 条件);

例として、年齢が30歳以上の社員を取得します。

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

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

AsEnumerable()で行をLINQ対象に変換し、Whereで条件を指定しています。

3-3. DataRowから列の値を取得する方法

DataRowから値を取得する方法には、主に次の2つがあります。

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

または、

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

LINQでDataTableを扱う場合は、Field<T>を使う書き方がおすすめです。

理由は、型が明確になり、コードの可読性が高くなるためです。

3-4. Field<T>を使って型安全に値を取得する

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

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

たとえば、給与が40万円以上の社員を取得する場合は次のように書けます。

C#
var highSalaryEmployees = table.AsEnumerable()
.Where(row => row.Field<decimal>("Salary") >= 400000m);

row["Salary"]をキャストするよりも、LINQ式の中で読みやすくなります。

3-5. DBNullやnullを扱うときの注意点

DataTableでは、値が存在しない場合にnullではなくDBNull.Valueが入ることがあります。

そのため、次のようなコードはエラーになる可能性があります。

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

対象列にDBNullが入る可能性がある場合は、Nullable型を使います。

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

文字列の場合も、nullの可能性を考慮して書くと安全です。

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

部分一致検索などで文字列メソッドを使う場合は、次のように書くと安全です。

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

4. WhereでDataTableの行を条件抽出する

4-1. Whereで特定条件に一致する行を取得する

Whereを使うと、条件に一致する行だけを抽出できます。

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

取得した結果はIEnumerable<DataRow>です。

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

この例では、部署が「開発」の社員だけを取得しています。

4-2. 文字列列を条件にして絞り込む

文字列列を条件にする場合は、Field<string>を使います。

C#
var salesEmployees = table.AsEnumerable()
.Where(row => row.Field<string>("Department") == "営業");

大文字・小文字を区別せずに比較したい場合は、StringComparisonを使う方法もあります。

C#
var result = table.AsEnumerable()
.Where(row => string.Equals(
row.Field<string>("Department"),
"sales",
StringComparison.OrdinalIgnoreCase));

日本語の部署名のように大文字・小文字の区別が不要な場合は、単純な比較で十分なことが多いです。

4-3. 数値列を条件にして絞り込む

数値列を条件にする場合は、Field<int>Field<decimal>を使います。

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

給与が40万円以上の社員を取得する例です。

C#
var highSalaryEmployees = table.AsEnumerable()
.Where(row => row.Field<decimal>("Salary") >= 400000m);

数値列にDBNullが入る可能性がある場合は、Nullable型を使います。

C#
var result = table.AsEnumerable()
.Where(row => row.Field<decimal?>("Salary") >= 400000m);

4-4. 複数条件をAND・ORで指定する

複数条件を指定する場合は、C#の通常の論理演算子を使います。

AND条件は&&です。

C#
var result = table.AsEnumerable()
.Where(row =>
row.Field<string>("Department") == "開発" &&
row.Field<int>("Age") >= 35);

OR条件は||です。

C#
var result = table.AsEnumerable()
.Where(row =>
row.Field<string>("Department") == "営業" ||
row.Field<string>("Department") == "人事");

条件が複雑になる場合は、適度に改行すると読みやすくなります。

C#
var result = table.AsEnumerable()
.Where(row =>
{
var department = row.Field<string>("Department");
var age = row.Field<int>("Age");

return department == "開発" && age >= 30;
});

4-5. Containsで部分一致検索する

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

C#
var result = table.AsEnumerable()
.Where(row => row.Field<string>("Name").Contains("田"));

ただし、対象列がnullになる可能性がある場合は注意が必要です。

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

複数列を対象に検索することもできます。

C#
string keyword = "開発";

var result = table.AsEnumerable()
.Where(row =>
(row.Field<string>("Name") ?? "").Contains(keyword) ||
(row.Field<string>("Department") ?? "").Contains(keyword));

検索画面やDataGridViewの絞り込み機能でよく使う書き方です。

4-6. 日付列を条件にして抽出する

日付列を条件にする場合は、Field<DateTime>を使います。

C#
var result = table.AsEnumerable()
.Where(row => row.Field<DateTime>("JoinDate") >= new DateTime(2020, 1, 1));

特定期間に入社した社員を抽出する例です。

C#
DateTime from = new DateTime(2020, 1, 1);
DateTime to = new DateTime(2022, 12, 31);

var result = table.AsEnumerable()
.Where(row =>
row.Field<DateTime>("JoinDate") >= from &&
row.Field<DateTime>("JoinDate") <= to);

日付列にDBNullが入る可能性がある場合は、Nullable型を使います。

C#
var result = table.AsEnumerable()
.Where(row =>
{
DateTime? joinDate = row.Field<DateTime?>("JoinDate");
return joinDate.HasValue && joinDate.Value >= new DateTime(2020, 1, 1);
});

5. SelectでDataTableの必要な列だけ取り出す

5-1. Selectで任意の列を取得する基本

Selectを使うと、各行から必要な値だけを取り出せます。

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

結果はIEnumerable<string>になります。

C#
foreach (var name in names)
{
Console.WriteLine(name);
}

1つの列だけを一覧にしたい場合に便利です。

5-2. 匿名型に変換して必要な列だけ取得する

複数列を取り出したい場合は、匿名型に変換できます。

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

使用例は次のとおりです。

C#
foreach (var employee in employees)
{
Console.WriteLine($"{employee.Id}: {employee.Name} - {employee.Department}");
}

画面表示用のデータや、必要最小限のデータだけを扱いたい場合に向いています。

5-3. 配列やListに変換する

LINQの結果は、ToArrayToListで配列・リストに変換できます。

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

配列にする場合は次のように書きます。

C#
string[] nameArray = table.AsEnumerable()
.Select(row => row.Field<string>("Name"))
.ToArray();

後続処理で繰り返し利用する場合は、ToList()で確定させておくと扱いやすくなります。

5-4. 列名一覧を取得する

DataTableの列名一覧を取得したい場合は、Columnsに対してCast<DataColumn>()を使います。

C#
var columnNames = table.Columns
.Cast<DataColumn>()
.Select(column => column.ColumnName)
.ToList();

列名を画面に表示したい場合や、動的に列を処理したい場合に使えます。

C#
foreach (var columnName in columnNames)
{
Console.WriteLine(columnName);
}

DataTable.ColumnsもそのままではLINQしにくいため、Cast<DataColumn>()を使うのがポイントです。

5-5. Selectで値を加工して取得する

Selectでは、値をそのまま取得するだけでなく、加工して取得できます。

C#
var displayList = table.AsEnumerable()
.Select(row => new
{
DisplayName = $"{row.Field<int>("Id")}:{row.Field<string>("Name")}",
MonthlySalary = row.Field<decimal>("Salary"),
YearlySalary = row.Field<decimal>("Salary") * 12
});

給与に単位を付けて文字列化することもできます。

C#
var salaryTexts = table.AsEnumerable()
.Select(row => $"{row.Field<string>("Name")}:{row.Field<decimal>("Salary"):N0}円");

LINQのSelectは、DataTableのデータを表示用データに変換するときに非常に便利です。

6. CopyToDataTableでLINQの結果をDataTableに戻す

6-1. CopyToDataTableとは

CopyToDataTableは、LINQで取得したIEnumerable<DataRow>を新しいDataTableに変換するメソッドです。

たとえば、Whereで抽出した結果を再びDataTableとして扱いたい場合に使います。

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

DataGridViewのデータソースに設定する場合などに便利です。

C#
dataGridView1.DataSource = resultTable;

6-2. Whereの抽出結果をDataTableに変換する

部署が「営業」の行だけを抽出し、DataTableに変換する例です。

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

DataTable salesTable = query.CopyToDataTable();

これで、営業部の社員だけを含む新しいDataTableが作成されます。

元のDataTableを直接変更するわけではなく、抽出結果をもとにした別のDataTableになります。

6-3. CopyToDataTableで新しいDataTableを作成する

CopyToDataTableを使うと、抽出された行の構造をもとに新しいDataTableが作成されます。

C#
DataTable newTable = table.AsEnumerable()
.Where(row => row.Field<int>("Age") >= 30)
.CopyToDataTable();

このnewTableには、元のtableと同じ列構成が引き継がれます。

ただし、Selectで匿名型に変換した結果にはCopyToDataTableを直接使えません。

C#
// これはDataRowではなく匿名型なのでCopyToDataTableできない
var query = table.AsEnumerable()
.Select(row => new
{
Name = row.Field<string>("Name"),
Age = row.Field<int>("Age")
});

CopyToDataTableは基本的にIEnumerable<DataRow>に対して使うものだと考えると分かりやすいです。

6-4. 抽出結果が0件のときに発生するエラー

CopyToDataTableでよくあるエラーが、抽出結果が0件のときの例外です。

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

このように該当する行が1件もない場合、CopyToDataTableでエラーになります。

代表的なエラーメッセージは次のようなものです。

ソースにDataRowが含まれていません。

そのため、CopyToDataTableを使う前には、0件チェックを行うのが安全です。

6-5. 0件でも空のDataTableを返す安全な書き方

抽出結果が0件でも空のDataTableを返したい場合は、Any()でチェックします。

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

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

table.Clone()は、元のDataTableと同じ列構成を持つ空のDataTableを作成します。

この書き方なら、検索結果が0件でもDataGridViewなどに安全にバインドできます。

C#
dataGridView1.DataSource = resultTable;

CopyToDataTableを使う場合は、0件チェックをセットで覚えておくと実務で役立ちます。

7. 並び替え・集計・重複除外などの応用操作

7-1. OrderBy・OrderByDescendingで並び替える

OrderByを使うと昇順で並び替えできます。

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

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

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

並び替えた結果をDataTableに戻す場合は、CopyToDataTableを使います。

C#
DataTable sortedTable = table.AsEnumerable()
.OrderByDescending(row => row.Field<decimal>("Salary"))
.CopyToDataTable();

7-2. GroupByで列ごとにグループ化する

GroupByを使うと、特定列ごとにデータをまとめられます。

C#
var groups = table.AsEnumerable()
.GroupBy(row => row.Field<string>("Department"));

部署ごとの人数を取得する例です。

C#
var summary = table.AsEnumerable()
.GroupBy(row => row.Field<string>("Department"))
.Select(g => new
{
Department = g.Key,
Count = g.Count()
});

結果を表示します。

C#
foreach (var item in summary)
{
Console.WriteLine($"{item.Department}: {item.Count}人");
}

部署別、カテゴリ別、年月別の集計などでよく使います。

7-3. Sum・Average・Countで集計する

LINQでは、SumAverageCountなどを使って集計できます。

給与の合計を求める例です。

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

平均年齢を求める例です。

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

開発部の人数を数える例です。

C#
int devCount = table.AsEnumerable()
.Count(row => row.Field<string>("Department") == "開発");

条件付き集計も簡潔に書けます。

7-4. Distinctで重複データを除外する

部署名の重複を除いて一覧を取得する例です。

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

結果は、重複のない部署名一覧になります。

C#
foreach (var department in departments)
{
Console.WriteLine(department);
}

複数列で重複を判定したい場合は、匿名型を使えます。

C#
var uniqueItems = table.AsEnumerable()
.Select(row => new
{
Department = row.Field<string>("Department"),
Age = row.Field<int>("Age")
})
.Distinct()
.ToList();

7-5. Any・Allで条件に一致するデータの有無を確認する

Anyは、条件に一致するデータが1件でもあるかを確認します。

C#
bool exists = table.AsEnumerable()
.Any(row => row.Field<string>("Department") == "開発");

Allは、すべての行が条件を満たすかを確認します。

C#
bool allAdults = table.AsEnumerable()
.All(row => row.Field<int>("Age") >= 20);

CopyToDataTable前の0件チェックにもAnyはよく使います。

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

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

7-6. FirstOrDefaultで最初の1件を取得する

条件に一致する最初の1件を取得したい場合は、FirstOrDefaultを使います。

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

結果が存在しない場合はnullになるため、nullチェックが必要です。

C#
if (row != null)
{
Console.WriteLine(row.Field<string>("Name"));
}
else
{
Console.WriteLine("該当データはありません。");
}

ID検索や単一レコード取得でよく使う書き方です。

8. DataTableをLINQで更新・加工する方法

8-1. 条件に一致する行の値を更新する

LINQで条件に一致する行を取得し、その行の値を更新できます。

C#
var rows = table.AsEnumerable()
.Where(row => row.Field<string>("Department") == "営業");

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

この例では、営業部の社員の給与を1万円増やしています。

LINQは抽出に使い、更新処理はforeachで行うのが分かりやすいです。

8-2. 特定列に一括で値を設定する

すべての行に対して特定列の値を設定することもできます。

C#
foreach (var row in table.AsEnumerable())
{
row["Department"] = row.Field<string>("Department") ?? "未設定";
}

条件付きで一括設定する場合は、次のように書けます。

C#
foreach (var row in table.AsEnumerable()
.Where(r => r.Field<int>("Age") >= 40))
{
row["Department"] = "ベテラン";
}

ただし、元のDataTableを直接更新するため、意図しない変更に注意が必要です。

8-3. LINQで取得した結果をforeachで処理する

LINQの結果は、foreachで1行ずつ処理できます。

C#
var targetRows = table.AsEnumerable()
.Where(row => row.Field<decimal>("Salary") >= 400000m);

foreach (var row in targetRows)
{
Console.WriteLine($"{row.Field<string>("Name")}:{row.Field<decimal>("Salary")}");
}

更新、ログ出力、画面表示、別処理への受け渡しなど、さまざまな用途に使えます。

8-4. 元のDataTableを変更する場合の注意点

AsEnumerable()で取得したDataRowは、元のDataTableの行を参照しています。

そのため、次のように値を変更すると、元のDataTableも変更されます。

C#
var row = table.AsEnumerable()
.FirstOrDefault(r => r.Field<int>("Id") == 1);

if (row != null)
{
row["Name"] = "佐藤太郎";
}

抽出結果だけを別データとして扱いたい場合は、CopyToDataTableで別のDataTableを作成するなど、元データへの影響を意識しましょう。

9. DataTable.SelectとLINQの違い

9-1. DataTable.Selectの特徴

DataTableには、もともとSelectメソッドがあります。

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

DataTable.Selectは、文字列で条件式を指定して行を抽出します。

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

簡単な条件抽出や並び替えであれば、DataTable.Selectでも対応できます。

9-2. LINQを使うメリット

LINQを使うメリットは、C#の構文で条件を書けることです。

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

文字列の条件式ではなく、型のあるコードとして書けるため、可読性や保守性が高くなります。

また、WhereSelectGroupByOrderBySumAverageなどを組み合わせやすい点も大きなメリットです。

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

9-3. DataTable.Selectのほうが向いているケース

DataTable.Selectが向いているケースもあります。

たとえば、次のような場合です。

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

単純な条件で、すぐにDataRow[]が欲しい場合はDataTable.Selectでも十分です。

また、既存コードでDataTable.Selectが多く使われている場合は、無理にすべてLINQへ置き換える必要はありません。

ただし、条件が複雑になったり、集計や変換を行ったりする場合はLINQのほうが書きやすくなります。

9-4. 可読性・型安全性・保守性の比較

DataTable.Selectは条件を文字列で書くため、列名の間違いや型の不一致に気づきにくい場合があります。

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

一方、LINQではC#の式として条件を書けます。

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

Field<T>で型を明示できるため、処理の意図が分かりやすくなります。

複雑な条件、複数の変換、集計を行う場合は、LINQのほうが保守しやすいことが多いです。

9-5. パフォーマンス面で注意すべきこと

LINQは便利ですが、大量データを扱う場合はパフォーマンスにも注意が必要です。

たとえば、同じLINQクエリを何度も実行すると、そのたびに列挙が発生することがあります。

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

int count = query.Count();
decimal total = query.Sum(row => row.Field<decimal>("Salary"));

必要に応じてToList()で一度結果を確定させるとよい場合があります。

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

int count = list.Count;
decimal total = list.Sum(row => row.Field<decimal>("Salary"));

数万件以上の大量データを頻繁に処理する場合は、データベース側で絞り込む、List<T>や専用クラスへ変換するなども検討しましょう。

10. DataTable×LINQでよくあるエラーと解決策

10-1. AsEnumerableが見つからない場合

AsEnumerableが見つからない場合は、次の点を確認してください。

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

さらに、.NET FrameworkではSystem.Data.DataSetExtensionsへの参照が必要です。

エラー例です。

'DataTable' に 'AsEnumerable' の定義が含まれていません

解決策は、参照設定にSystem.Data.DataSetExtensionsを追加することです。

Visual Studioの場合は、プロジェクトの参照から追加できます。

10-2. CopyToDataTableで「ソースにDataRowが含まれていません」と出る場合

CopyToDataTableは、対象のIEnumerable<DataRow>が空の場合にエラーになります。

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

DataTable result = query.CopyToDataTable();

安全に書くには、Any()でチェックします。

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

検索機能などでは、0件になるケースを必ず考慮しましょう。

10-3. 指定した列名が存在しない場合

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

C#
var value = row.Field<string>("NotExistsColumn");

列名を動的に扱う場合は、事前に存在確認をすると安全です。

C#
if (table.Columns.Contains("Name"))
{
var names = table.AsEnumerable()
.Select(row => row.Field<string>("Name"));
}

列名のスペルミスは実務でもよくあるため、定数化するのもおすすめです。

C#
const string ColumnName = "Name";

10-4. 型変換でエラーになる場合

列の型とField<T>で指定した型が一致していないとエラーになります。

C#
// Age列がintなのにstringとして取得しようとしている
string age = row.Field<string>("Age");

正しくは次のように書きます。

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

文字列として表示したい場合は、取得後に変換します。

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

DataColumnの型を確認して、Field<T>の型を合わせることが重要です。

10-5. DBNullを文字列や数値として扱ってエラーになる場合

DBNullを通常の値として扱うとエラーになることがあります。

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

Age列にDBNullが入る可能性があるなら、Nullable型にします。

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

文字列の場合は、null合体演算子を使うと安全です。

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

数値列で未設定を0として扱う例です。

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

DataTableではnullDBNullの扱いに注意しましょう。

11. 実務で使えるDataTable LINQサンプル集

11-1. 条件に一致する行だけ別DataTableにする

検索条件に一致する行だけを別のDataTableにする例です。

C#
string department = "開発";

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

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

DataGridViewに表示する場合は次のように使えます。

C#
dataGridView1.DataSource = resultTable;

検索結果が0件でもエラーにならない安全な書き方です。

11-2. 必要な列だけを抽出したDataTableを作る

Selectで匿名型にした結果は、そのままCopyToDataTableできません。

必要な列だけのDataTableを作りたい場合は、新しいDataTableを定義して行を追加します。

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

var rows = table.AsEnumerable()
.Select(row => new
{
Name = row.Field<string>("Name"),
Department = row.Field<string>("Department")
});

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

これで、必要な列だけを持つDataTableを作成できます。

11-3. 複数条件で検索してDataGridViewに表示する

画面の検索条件をもとに絞り込む例です。

C#
string keyword = "田";
string department = "開発";
int minAge = 30;

var query = table.AsEnumerable()
.Where(row =>
(string.IsNullOrEmpty(keyword) ||
(row.Field<string>("Name") ?? "").Contains(keyword)) &&
(string.IsNullOrEmpty(department) ||
row.Field<string>("Department") == department) &&
row.Field<int>("Age") >= minAge);

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

dataGridView1.DataSource = resultTable;

検索画面では、未入力の条件を無視するように書くと実用的です。

11-4. 重複を除いて一覧を作成する

部署一覧のように、重複を除いたリストを作る例です。

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

ComboBoxに設定する場合は次のように使えます。

C#
comboBox1.DataSource = departments;

マスタデータがない場合でも、DataTableから一覧を作成できます。

11-5. グループごとの件数や合計を取得する

部署ごとの人数と給与合計を取得する例です。

C#
var summary = table.AsEnumerable()
.GroupBy(row => row.Field<string>("Department"))
.Select(g => new
{
Department = g.Key,
Count = g.Count(),
TotalSalary = g.Sum(row => row.Field<decimal>("Salary")),
AverageSalary = g.Average(row => row.Field<decimal>("Salary"))
});

結果を表示します。

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

集計表やレポート作成でよく使うパターンです。

12. C#のDataTableをLINQで扱うときのベストプラクティス

12-1. Field<T>を使って型を明確にする

DataRowの値を取得するときは、できるだけField<T>を使いましょう。

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

次のような書き方よりも、型が分かりやすくなります。

C#
int age = (int)row["Age"];

Field<T>を使うことで、LINQ式の中でも読みやすいコードになります。

12-2. CopyToDataTable前にAnyで0件チェックする

CopyToDataTableを使う場合は、抽出結果が0件の可能性を考慮しましょう。

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

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

このパターンを使えば、0件でも空のDataTableを返せます。

検索機能や画面表示では特に重要です。

12-3. 大量データではパフォーマンスを考慮する

DataTableとLINQは便利ですが、大量データをすべてメモリ上で処理する点には注意が必要です。

数万件、数十万件のデータを頻繁に処理する場合は、次のような対策を検討しましょう。

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

同じ結果を何度も使う場合は、ToList()で一度確定させると無駄な再評価を避けられます。

また、データベースから取得する段階で条件を絞り込むほうが効率的な場合も多いです。

12-4. DataTableにこだわらずListやクラス化も検討する

実務では、DataTableよりも専用クラスのリストに変換したほうが扱いやすい場合があります。

C#
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string Department { get; set; }
public int Age { get; set; }
public decimal Salary { get; set; }
public DateTime JoinDate { get; set; }
}

DataTableからList<Employee>へ変換する例です。

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

型が明確になり、後続処理やテストがしやすくなります。

12-5. 読みやすいLINQ式を書くためのポイント

LINQは便利ですが、1行に詰め込みすぎると読みにくくなります。

読みにくい例です。

C#
var result = table.AsEnumerable().Where(r => r.Field<string>("Department") == "開発" && r.Field<int>("Age") >= 30).OrderByDescending(r => r.Field<decimal>("Salary")).Select(r => new { Name = r.Field<string>("Name"), Salary = r.Field<decimal>("Salary") });

読みやすくするには、適度に改行します。

C#
var result = table.AsEnumerable()
.Where(row =>
row.Field<string>("Department") == "開発" &&
row.Field<int>("Age") >= 30)
.OrderByDescending(row => row.Field<decimal>("Salary"))
.Select(row => new
{
Name = row.Field<string>("Name"),
Salary = row.Field<decimal>("Salary")
});

条件が複雑な場合は、変数に分けるのも有効です。

C#
var result = table.AsEnumerable()
.Where(row =>
{
string department = row.Field<string>("Department");
int age = row.Field<int>("Age");

return department == "開発" && age >= 30;
});

LINQは短く書くことよりも、処理の意図が伝わることを重視しましょう。

まとめ

C#のDataTableをLINQで操作するには、まずAsEnumerable()を使ってIEnumerable<DataRow>として扱えるようにします。

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

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

Whereを使えば条件抽出、Selectを使えば必要な列の取得、OrderByを使えば並び替え、GroupByを使えば集計ができます。

抽出結果を再びDataTableとして扱いたい場合は、CopyToDataTableを使います。

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

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

ただし、CopyToDataTableは0件の場合にエラーになるため、Any()でチェックしてから使うのが安全です。

また、列の値を取得するときは、row["列名"]よりもField<T>を使うことで、型が明確で読みやすいコードになります。

DataTableは既存システムや業務アプリでよく使われるデータ構造ですが、LINQを組み合わせることで、検索、抽出、集計、変換をシンプルに書けます。

特に、AsEnumerableWhereSelectCopyToDataTableの4つを押さえておくと、C#でのDataTable操作を効率よく実装できるようになります。