2015年1月14日水曜日

Import時の重複チェックにLINQ(GroupBy)を利用する

外部のシステムからデータをインポートする機能は、多くのシステムで必要になってきます。CSVなりXMLなりの標準化された形式で送られたデータを、一括でシステムに取り込む機能です。

この場合、普通の入力とは異なり、一括で処理するゆえのメンドクササがありますよね。

たとえば、ユーザ情報を外部システムからインポートする場合。想定するケースによりますが、ユーザIDがインポートデータ中に重複して存在する可能性があったりすると、せめて取り込みの事前のチェックを行っておきたいケースもあると思います。が、これが結構面倒。

これをGroupByメソッドを活用して、なるべくシンプルに記述してみます。想定する前提はこんな感じだとします。
  • インポートするユーザ情報データは、すでにオブジェクトへのマッピングがされており、コレクション化されている前提。
  • ユーザ情報データは「UserInfo」クラスに格納されており、ユーザIDは文字列のメンバUserIdとして存在する。
  • ユーザ情報データのコレクションを入力として、ユーザIDの重複チェックを行う静的メソッド「ValidateNotDuplicated」を作成する。
  • このメソッドは、ユーザIDの重複があった場合、コンソールにエラーメッセージを出力し、メソッドの戻り値はfalseを返す。重複がなければそのままtrueを返す。
  • エラーメッセージには、重複しているユーザIDを表示する。複数のユーザIDが重複していた場合は、そのすべてを表示する。
このような処理を書くと、こんな感じになりますね。

public static bool ValidateNotDuplicated(IEnumerable<UserInfo> users)
{
  var dupusers = users.GroupBy(u => u.UserId)
    .Where(g => g.Count() > 1)
    .Select(g => g.Key)
    .ToArray();

  if (dupusers.Length > 0)
  {
    Console.WriteLine("User:{0} was duplicated.", string.Join(",", dupusers);
    return false;
  }

  return true;
}

つまり、「コレクションをUserIdでグループ化し(GroupBy)」、「そのうち2つ以上存在するものを選び出し(Where)」、「キーとなっているUserIdのみを対象として(Select)」、「配列に変換する(ToArray)」。という処理を行っています。

ちょっとだけバリエーションを考えて見ましょうか。たとえば、
  • 「UserInfo」クラスには、データが更新された時間を示す「Updated」メンバ(DateTime型)も持っている。
  • このユーザ情報データのコレクションを入力として、ユーザIDが重複していた場合、「Updated」が最新のもの以外を取り除いたコレクションを返す、静的メソッド「SelectLatest」を作成する。
この場合ならもっとシンプルで、

public static IEnumerable<UserInfo> SelectLatest(IEnumerable<UserInfo> users)
{
  return users.GroupBy(u => u.UserId)
    .Select(g => g.OrderByDescending(u => u.Updated).First());
}
これだけになりますね。


0 件のコメント:

コメントを投稿