読者です 読者をやめる 読者になる 読者になる

redwarrior’s diary

C#, ASP.NET, WEB, G*などの雑多な情報

LINQを使用したメソッドの共通化の試行錯誤

LINQを使用して、DBから取得したデータでDTOを構築するメソッドを作成していると、インターフェイスは違うのに処理の流れがほぼ同じになる事ってありませんか?

どういうことかと言うと、片方の処理では、単純なLINQを使用しているのでIQueryable<T>を返します。もう片方の処理では、GroupByを使用しているので、IQueryable<IGrouping<int, T>>を返します。

このIQueryable<T>とIGrouping<int, T>の変数に対して呼び出すメソッドと処理フローがソースコード上は、同じにすることが出来ます。

この処理フローをメソッドとして抽出すると、パラメータの型だけが異なり中身が同じメソッドが出来上がります。

CirculationDto BuildDto(IQueryable<Circulation> circulations)
{
    var dto = new CirculationDto();
    var first = circulations.First();
    dto.DistrictId = first.Agency.DistrictId;
    dto.AgencyId = first.AgencyId;
    dto.Daily =
        circulations.Where(circulation => circulation.PaperId == 1)
            .Sum(circulation => circulation.value);
    dto.Weekly =
        circulations.Where(circulation => circulation.PaperId == 2)
            .Sum(circulation => circulation.value);

    return dto;
}

IQueryable<T>を引数に取るメソッド

CirculationDto BuildDto(IGrouping<int, Circulation> circulations)
{
    var dto = new CirculationDto();
    var first = circulations.First();
    dto.DistrictId = first.Agency.DistrictId;
    dto.AgencyId = first.AgencyId;
    dto.Daily =
        circulations.Where(circulation => circulation.PaperId == 1)
            .Sum(circulation => circulation.value);
    dto.Weekly =
        circulations.Where(circulation => circulation.PaperId == 2)
            .Sum(circulation => circulation.value);

    return dto;
}

IGrouping<int, T>を引数に取るメソッド

このメソッドを共通化できるか試行錯誤してみました。

共通の親インターフェイスであるIEnumerable<T>をパラメータに持つメソッドを作成する

最初、これを試してみましたが、IEnumerable<T>のLINQメソッドを使用すると、DBから全件を取得してメモリ上で絞り込みを行うという情報を見つけたため、やめました。

IQueryable<T>をパラメータに持つメソッドを使用し、IGrouping<int, T>型の変数は呼び出すときにAsQueryableメソッドを使用する

基本的には上手くいきそうでしたが、別の問題が発生してしまいました。IQueryable<T>でSelectメソッドでインデックスのついた版を実行したところ、DBから取得するときに例外が発生したため、元に戻してしまいました。

共通化しない

もしかしたらできるのかもしれませんが、今回は共通化しない事にしました。

処理フローは、「IQueryable<T>は、foreach文の中でほとんどの処理を行う。」、
「IGrouping<int, T>は、共通プロパティはFirstメソッドで最初の1データを取得して使用し、集計するプロパティはforeach文で集計を行う。」 といった感じで、それぞれのインターフェイスを考慮したフローに変更しました。

以下が変更後のソースです。

CirculationDto BuildDto(IQueryable<Circulation> circulations)
{
    var dto = new CirculationDto();

    foreach (var circulation in circulations)
    {
        var agency = circulation.Agency;

        dto.DistrictId = agency.DistrictId;
        dto.AgencyId = agency.AgencyId;

        switch (circulation.OrganPaperId)
        {
            case 1:
                dto.Daily = circulation.IncreaseAndDecrease;
                dto.CurrentDaily = circulation.Current;
                break;
            case 2:
                dto.Weekly = circulation.IncreaseAndDecrease;
                dto.CurrentWeekly = circulation.Current;
                break;
        }
    }

    return dto;
}

IQueryable<T>を引数に取るメソッド

CirculationDto BuildDto(IGrouping<int, Circulation> circulations)
{
    var dto = new CirculationDto
                  {
                      DistrictId = circulations.First().Agency.DistrictId,
                      AgencyId = circulations.Key
                  };

    foreach (var circulation in circulations)
    {
        switch (circulation.OrganPaperId)
        {
            case 1:
                dto.Daily = circulation.IncreaseAndDecrease;
                dto.CurrentDaily = circulation.Current;
                break;
            case 2:
                dto.Weekly = circulation.IncreaseAndDecrease;
                dto.CurrentWeekly = circulation.Current;
                break;
        }
    }

    return dto;
}

IGrouping<int, T>を引数に取るメソッド

まだまだ、LINQに慣れていない部分もあるので、もっといい方法があるのかもしれないですね。情報があれば教えてください。