LINQを活用したDataTableの重複行排除テクニック

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内部のパフォーマンスを最適化できます。

タグ: LINQ datatable IEqualityComparer C# データ重複排除

8月22日 01:06 投稿