トップ回答者
LINQで型違いの複数JOIN

質問
-
方法:内部結合を実行する(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); } }
回答
-
//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
-
型を同じにしてやればいいんです。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
すべての返信
-
//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
-
型を同じにしてやればいいんです。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
-
逆で、幅は広いです。where句は比較式というよりもboolが返るなら何でも書けます。結合に関係ない式でも書けます。
where c.MemberID == m.MemberID && c.MemberKind == m.MemberKind
&& c.CustomerID > 2
とか。
クロス結合の問題は、m×n通りの組み合わせ全てを試行してしまうことです。
ついでに指摘。そもそも匿名型の初期化には、メンバの名前が必須です。 new { ID = customer.MemberID } などと記述してください。