none
LINQで型違いの複数JOIN RRS feed

  • 質問

  • 方法:内部結合を実行する(C# プログラミング ガイド)の「複合キーの結合の例」を参考に以下のコードを書いてみました。
    元コードとの違いは結合条件が元コードでは new {string型, string型} equals new {string型, string型}でしたが
    今回やりたかったのは new {int型, byte型} equals new {int?型, byte型}にしてみました。
    するとビルドエラー
    「 join 句のいずれかの式の型が正しくありません。'Join' の呼び出しで型を推論できませんでした。 」が発生します。
    条件を newを使わず、「customer.MemberID equals member.MemberID」(ケース1) とした場合はビルドOK
    同じ条件でnewを使い、「new{customer.MemberID} equals new{member.MemberID}」(ケース2)でもビルドNG
    型がint型とint?型と異なっているのが原因と思ってますが、このような場合どのように対処したらよいのでしょうか?
    ※SQLは触ったことがないので違う書き方があるよ、などの情報も大歓迎

        class Customer    
        {
            public int CustomerID { get; set; }
            public int? MemberID { get; set; }
            public byte MemberKind { get; set; }
            public string Name { get; set; }
        }
    
        class Member
        {
            public int MemberID { get; set; }
            public byte MemberKind { get; set; }
            public string Address { get; set; }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                //元データ
                List<Customer> customers = new List<Customer> {
                        new Customer { CustomerID = 1, MemberID = null, MemberKind = 0, Name = "一郎" },
                        new Customer { CustomerID = 2, MemberID = 1, MemberKind = 1, Name = "二郎" },
                        new Customer { CustomerID = 3, MemberID = 1, MemberKind = 2, Name = "三郎" }
                };
    
                List<Member> members = new List<Member> {
                        new Member { MemberID = 1, MemberKind = 1, Address = "東京都" },
                        new Member { MemberID = 2, MemberKind = 1, Address = "大阪府" },
                        new Member { MemberID = 1, MemberKind = 2, Address = "北海道" },
                };
     
                //custormer情報とmember情報を結合した情報が欲しい
                var query = from customer in customers
                            join member in members
                            //on customer.MemberID equals member.MemberID           //ケース1 ←これならビルド通る
                            //on new{customer.MemberID} equals new{member.MemberID} //ケース2 ←ビルド通らない
                            on new { customer.MemberID, customer.MemberKind }
                            equals new { member.MemberID, member.MemberKind }       //ケース3 ←ビルド通らない
                            select customer.Name + " (" + member.Address + ") ";
    
    
                foreach (var info in query)
                {
                    Console.WriteLine(info);
                }
            }
    
    2009年5月31日 11:58

回答

  • //on customer.MemberID equals member.MemberID           //ケース1 ←これならビルド通る
    
    

    int と int? を比較する際は暗黙に型変換が行われます。ですので問題ありません。

    //on new{customer.MemberID} equals new{member.MemberID} //ケース2 ←ビルド通らない
    
    

    そもそも匿名型の初期化には、メンバの名前が必須です。 new { ID = customer.MemberID } などと記述してください。

    on new
     { customer.MemberID, customer.MemberKind }
    equals new
     { member.MemberID, member.MemberKind }       //ケース3 ←ビルド通らない
    
    

    equals の両辺は同じ型でなければなりません。「同じ型」の定義は、匿名型の場合、メンバ数が同じで、すべてのメンバの型と名前が同じであることです。ケース 2 の場合もそうですが、片方の MemberID は int、もう片方の MemberID は int? なので、そのまま初期化するだけではメンバの型が違う == 違う匿名型と言うことになってしまいます。

    ですので、初期化する際、int の MemberID を int? に変換してやれば同じ匿名型を使用できることになります。

    • 回答としてマーク C.John 2009年6月2日 11:28
    2009年5月31日 13:06
  • 型を同じにしてやればいいんです。int?をintにキャストするとnullが通せないので、intをint?にキャストしたり。
    でキャストすると今度は無名型が構築できなくなって、メンバ名を明示する羽目に。

    var query = from c in customers
      join m in members
      on new { MemberID = c.MemberID, c.MemberKind }
      equals new { MemberID = (int?)m.MemberID, m.MemberKind }
      select c.Name + " (" + m.Address + ") ";

    このアプローチだと比較内容が見づらいです。で、なんとなくクロス結合。こっちだと比較している部分が直感的で読みやすかったり。
    その代わりクロス結合の欠点がそのまま出ててるんでしょうが…。

    var query = from c in customers
      from m in members
      where c.MemberID == m.MemberID && c.MemberKind == m.MemberKind
      select c.Name + " (" + m.Address + ") ";
    • 回答としてマーク C.John 2009年6月2日 11:28
    2009年5月31日 13:42

すべての返信

  • //on customer.MemberID equals member.MemberID           //ケース1 ←これならビルド通る
    
    

    int と int? を比較する際は暗黙に型変換が行われます。ですので問題ありません。

    //on new{customer.MemberID} equals new{member.MemberID} //ケース2 ←ビルド通らない
    
    

    そもそも匿名型の初期化には、メンバの名前が必須です。 new { ID = customer.MemberID } などと記述してください。

    on new
     { customer.MemberID, customer.MemberKind }
    equals new
     { member.MemberID, member.MemberKind }       //ケース3 ←ビルド通らない
    
    

    equals の両辺は同じ型でなければなりません。「同じ型」の定義は、匿名型の場合、メンバ数が同じで、すべてのメンバの型と名前が同じであることです。ケース 2 の場合もそうですが、片方の MemberID は int、もう片方の MemberID は int? なので、そのまま初期化するだけではメンバの型が違う == 違う匿名型と言うことになってしまいます。

    ですので、初期化する際、int の MemberID を int? に変換してやれば同じ匿名型を使用できることになります。

    • 回答としてマーク C.John 2009年6月2日 11:28
    2009年5月31日 13:06
  • 型を同じにしてやればいいんです。int?をintにキャストするとnullが通せないので、intをint?にキャストしたり。
    でキャストすると今度は無名型が構築できなくなって、メンバ名を明示する羽目に。

    var query = from c in customers
      join m in members
      on new { MemberID = c.MemberID, c.MemberKind }
      equals new { MemberID = (int?)m.MemberID, m.MemberKind }
      select c.Name + " (" + m.Address + ") ";

    このアプローチだと比較内容が見づらいです。で、なんとなくクロス結合。こっちだと比較している部分が直感的で読みやすかったり。
    その代わりクロス結合の欠点がそのまま出ててるんでしょうが…。

    var query = from c in customers
      from m in members
      where c.MemberID == m.MemberID && c.MemberKind == m.MemberKind
      select c.Name + " (" + m.Address + ") ";
    • 回答としてマーク C.John 2009年6月2日 11:28
    2009年5月31日 13:42
  • なるほど、匿名型の使い方が分かりました。
    ありがとうございます。

    見やすさはクロス結合(←今回初めて知った用語)の方が分かりやすいですね。
    今回のケースではクロス結合を採用しようと思います。
    #ただ、なんとなく応用の幅は狭そうな印象ですが
    2009年6月2日 11:28
  • 逆で、幅は広いです。where句は比較式というよりもboolが返るなら何でも書けます。結合に関係ない式でも書けます。

      where c.MemberID == m.MemberID && c.MemberKind == m.MemberKind
        && c.CustomerID > 2

    とか。
    クロス結合の問題は、m×n通りの組み合わせ全てを試行してしまうことです。


    ついでに指摘。

    そもそも匿名型の初期化には、メンバの名前が必須です。 new { ID = customer.MemberID } などと記述してください。

    必須ではありません。例えば new { customer.MemberID } なら MemberIDというプロパティが作成されます。
    2009年6月2日 11:46