DataViewを使った重複排除も存在しますが、今回はLINQの柔軟性を活した方法を紹介します。
基本的な重複排除
特定列の組み合わせで重複を判定し、一意な行を抽出する実装例です。
using System;
using System.Data;
using System.Linq;
using System.Collections.Generic;
namespace DataProcessor
{
class Demo
{
static void Main(string[] args)
{
DataTable employeeTable = BuildSampleData();
Console.WriteLine("【重複含む元データ】");
DisplayTable(employeeTable);
Console.WriteLine("\n【重複排除後】");
var uniqueRows = employeeTable.AsEnumerable()
.Distinct(new RowComparer("emp_id", "dept_code", "location"));
DataTable filtered = uniqueRows.CopyToDataTable();
DisplayTable(filtered);
}
static DataTable BuildSampleData()
{
var table = new DataTable();
table.Columns.Add("emp_id", typeof(int));
table.Columns.Add("emp_name", typeof(string));
table.Columns.Add("dept_code", typeof(string));
table.Columns.Add("location", typeof(string));
var rowData = new[] {
(1, "tanaka", "dev", "tokyo"),
(1, "tanaka", "dev", "tokyo"),
(2, "suzuki", "sales", "osaka"),
(3, "yamada", "dev", "tokyo"),
(3, "yamada", "dev", "tokyo"),
(4, "kobayashi", "hr", "nagoya")
};
foreach (var (id, name, dept, loc) in rowData)
{
var r = table.NewRow();
r["emp_id"] = id;
r["emp_name"] = name;
r["dept_code"] = dept;
r["location"] = loc;
table.Rows.Add(r);
}
return table;
}
static void DisplayTable(DataTable dt)
{
foreach (DataRow r in dt.Rows)
{
Console.WriteLine($"{r["emp_id"]} | {r["emp_name"]} | {r["dept_code"]} | {r["location"]}");
}
}
}
public class RowComparer : IEqualityComparer<DataRow>
{
private readonly string[] _keyColumns;
public RowComparer(params string[] compareKeys)
{
_keyColumns = compareKeys;
}
public bool Equals(DataRow left, DataRow right)
{
if (ReferenceEquals(left, right)) return true;
if (left is null || right is null) return false;
foreach (var col in _keyColumns)
{
if (!left[col].Equals(right[col]))
return false;
}
return true;
}
public int GetHashCode(DataRow row)
{
int hash = 17;
foreach (var col in _keyColumns)
{
var val = row[col];
hash = hash * 31 + (val?.GetHashCode() ?? 0);
}
return hash;
}
}
}
汎用メソッド化
任意の列を動的に指定できる拡張メソッドとして実装すると、再利用性が高まります。
public static class DataTableExtensions
{
public static DataTable DistinctByColumns(
this DataTable source,
params string[] compareFields)
{
if (source is null) throw new ArgumentNullException(nameof(source));
if (compareFields is null || compareFields.Length == 0)
return source.Copy();
return source.AsEnumerable()
.Distinct(new FlexibleRowComparer(compareFields))
.CopyToDataTable();
}
}
public class FlexibleRowComparer : IEqualityComparer<DataRow>
{
private readonly string[] _fields;
public FlexibleRowComparer(string[] fields)
{
_fields = fields ?? throw new ArgumentNullException(nameof(fields));
}
public bool Equals(DataRow x, DataRow y)
{
return _fields.All(f => x[f].Equals(y[f]));
}
public int GetHashCode(DataRow obj)
{
unchecked
{
int hash = 19;
foreach (var f in _fields)
{
var value = obj[f];
hash = hash * 397 + (value?.GetHashCode() ?? 0);
}
return hash;
}
}
}
使用例
// 全列一致で重複判定
var result1 = dataTable.DistinctByColumns(
"product_id", "category", "price"
);
// 部分列のみで重複判定(他列は無視)
var result2 = dataTable.DistinctByColumns("phone_number");
GetHashCodeの実装に注意が必要です。衝突しにくい定数を選び、各列の値を組み合わせることで、Dictionary内部のパフォーマンスを最適化できます。