lycheejam's tech log

チラ裏のメモ帳 | プログラミングは苦手、インフラが得意なつもり。

C# EntityFrameworkでネストしたプロパティのデータを取得する

概要

タイトルですでに何言ってるかわからないですよね。自分でも表現の仕方がわからないです。
SQLで言うとLEFT JOINをネスト(LEFT JOINの中にLEFT JOIN)させてデータを取得する感じです。
それをLINQ to Entitiesでやりたかった。できたのでメモ

SELECT
    適当
FROM
  適当A AS A
  LEFT JOIN ( 
    SELECT * FROM 適当B
      LEFT JOIN (SELECT * FROM 適当C) AS C 
        ON 適当
  ) AS B
    ON 適当

環境

ソース

github.com

登場人物(オブジェクト)

最初にDBのリレーション(ER図)を見せておきます。テーブルの全体像です。

f:id:HM_Atlas:20180507015001p:plain

護衛艦テーブル

class SelfDefenseShip { //護衛艦
    public virtual EscortDivision EscortDivision { get; set; }  //所属
    public virtual HullCode HullCode { get; set; }  //艦種記号
    [Key]
    public int ShipNumber { get; set; }     //艦識別番号
    public string ShipName { get; set; }    //艦名
    public virtual ShipClass ShipClass { get; set; }    //艦種型
    public int StandardDisplacement { get; set; }   //基準排水量(トン)
    public int FullLoadDisplacement { get; set; }   //満載排水量(トン)
    public double FullLength { get; set; }  //全長(メートル)
    public double FullWidth { get; set; }   //全幅(メートル)
    public DateTime CommissionYear { get; set; } //就役(年)
}

隊テーブル

class EscortDivision {  //護衛隊
    [Key]
    public int EscortDivisionId { get; set; }   //護衛隊ID
    public string EscortDivisionName { get; set; }  //護衛隊名 ex.第1護衛隊
    public virtual EscortFlotilla EscortFlotilla { get; set; }  //所属護衛隊群
    public virtual ICollection<SelfDefenseShip> SelfDefenseShips { get; set; }   //所属艦艇
}

群テーブル

class EscortFlotilla {  //護衛隊群
    [Key]
    public int EscortFlotillaId { get; set; }   //護衛隊群ID
    public string EscortFlotillaName { get; set; }  //護衛隊群名 ex.第1護衛隊群
    public virtual ICollection<EscortDivision> EscortDivision { get; set; }
}

艦種テーブル

class HullCode {    //艦種別
    [Key]
    public int HullCodeId { get; set; } //艦種記号ID
    public string HullCodeSymbol { get; set; }  //艦種記号 ex.DD,DDG,DDH
    public virtual ICollection<SelfDefenseShip> SelfDefenseShips { get; set; }   //種別護衛艦
}

抽出目的のデータ(目指すべき姿)

言葉で表せば隊テーブルを元に隊が所属している上位組織である所属群のデータを取得し、さらに隊に所属する艦船のデータを取得します。
SQLで表すとこんな感じです。

SELECT
  Flotillas.EscortFlotillaName AS 所属護衛隊群
  , Divisions.EscortDivisionName AS 所属護衛隊
  , Ships.HullCodeSymbol AS 艦種記号
  , Ships.ShipNumber AS 艦船番号
  , Ships.ShipName AS 艦船名
FROM
  EscortDivisions AS Divisions 
  LEFT JOIN (SELECT * FROM EscortFlotillas) AS Flotillas 
    ON Divisions.EscortFlotilla_EscortFlotillaId = Flotillas.EscortFlotillaId 
  LEFT JOIN ( 
    SELECT
      * 
    FROM
      SelfDefenseShips 
      LEFT JOIN (SELECT * FROM HullCodes) AS Code 
        ON HullCode_HullCodeId = Code.HullCodeId
  ) AS Ships 
    ON Divisions.EscortDivisionId = Ships.EscortDivision_EscortDivisionId 
ORDER BY
  Flotillas.EscortFlotillaId
  , Divisions.EscortDivisionId
  , Ships.ShipNumber

こんな感じのデータが欲しいのです。

f:id:HM_Atlas:20180507020811p:plain

LINQ to Entitiesでデータを抽出

抽出でき”ない”コード

下記のコードだとIncludeで艦船情報(SelfDefenseShip)に紐付いているデータが全て抽出されているイメージでしたが艦種コード(HullCode)まで読み込まれていませんでした。

.Include(x => x.SelfDefenseShips)で更に続けてHullCodeを読み込もうとしてましたが候補に表示されませんし、奇妙な書き方をするとエラーで怒られます。

残念!

class ReadDataStore {
    public ICollection<EscortDivision> ReadDivisionAllData() {
        using (var db = new ShipsDbContext()) {
            db.Database.Log = sql => { Debug.Write(sql); };
            var dd = db.EscortDivisions.Include(x => x.EscortFlotilla)
                                       .Include(x => x.SelfDefenseShips)
                                       .ToList();
            return dd;
        }
    }
}

抽出できるコード

件のコードです。これで出来ました。
読み込んだSelfDefenseShipテーブルに対して個別にSelectで読みこむプロパティを指定する必要がありました。

class ReadDataStore {
    public ICollection<EscortDivision> ReadDivisionAllData() {
        using (var db = new ShipsDbContext()) {
            var dd = db.EscortDivisions.Include(x => x.EscortFlotilla)
                                       .Include(x => x.SelfDefenseShips
                                            .Select(y => y.HullCode))
                                       .ToList();
            return dd;
        }
    }
}

ちゃんとデータが出力されました!

f:id:HM_Atlas:20180507023057p:plain

参考サイト様

stackoverflow.com

雑感

GW前半戦の宿題を消化出来てよかったです。