スポンサーリンク

Linq to XML 入門 その4 結合(JOIN)その2

忘れた頃にやってくる連載記事です。

今回は、やらないと前回書いた左外部結合とグループ化結合について説明します。

また、サンプルで使用するXMLファイルで、前回との違いは、category.xmlにカテゴリを一つ追加しています。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<!--?xml version="1.0" encoding="utf-8" ?-->
<categories>
<category id="1">
<categoryname>飲料</categoryname>
</category>
<category id="2">
<categoryname>前菜</categoryname>
</category>
<category id="3">
<categoryname>主菜</categoryname>
</category>
<category id="4">
<categoryname>デザート</categoryname>
</category>
<category id="5">
<categoryname>お持ち帰り</categoryname>
</category>
</categories>
<!--?xml version="1.0" encoding="utf-8" ?--> <categories> <category id="1"> <categoryname>飲料</categoryname> </category> <category id="2"> <categoryname>前菜</categoryname> </category> <category id="3"> <categoryname>主菜</categoryname> </category> <category id="4"> <categoryname>デザート</categoryname> </category> <category id="5"> <categoryname>お持ち帰り</categoryname> </category> </categories>


  
    飲料
  
  
    前菜
  
  
    主菜
  
  
    デザート
  
  
    お持ち帰り
    

グループ化結合

グループ化結合は、階層データ構造を作成する場合に便利です。グループ化結合は、最初のコレクションの各要素と、2 番目のコレクションの相関関係を持つ要素のセットを組み合わせたものです。

使用するXMLは内部結合のものと同じです。

以下の例ではカテゴリごとの料理のリストを作成しています。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
XDocument menu = XDocument.Load(@"..\..\menu.xml");
XDocument category = XDocument.Load(@"..\..\category.xml");
var menuList = from c in category.Descendants("Category")
join m in menu.Descendants("item")
on
(string)c.Attribute("id")
equals
(string)m.Attribute("CategoryId")
into menusByCategory
select new {
ID = c.Attribute("id"),
CategoryName = c.Element("CategoryName").Value,
Menus = from me in menusByCategory select new { Name = me.Element("name").Value }
};
foreach (var c in menuList) {
Console.WriteLine("{0} - {1}", c.ID, c.CategoryName);
foreach (var m in c.Menus) {
Console.WriteLine("\t{0}", m.Name);
}
}
XDocument menu = XDocument.Load(@"..\..\menu.xml"); XDocument category = XDocument.Load(@"..\..\category.xml"); var menuList = from c in category.Descendants("Category") join m in menu.Descendants("item") on (string)c.Attribute("id") equals (string)m.Attribute("CategoryId") into menusByCategory select new { ID = c.Attribute("id"), CategoryName = c.Element("CategoryName").Value, Menus = from me in menusByCategory select new { Name = me.Element("name").Value } }; foreach (var c in menuList) { Console.WriteLine("{0} - {1}", c.ID, c.CategoryName); foreach (var m in c.Menus) { Console.WriteLine("\t{0}", m.Name); } }
XDocument menu = XDocument.Load(@"..\..\menu.xml");
XDocument category = XDocument.Load(@"..\..\category.xml");
 
var menuList = from c in category.Descendants("Category")
               join m in menu.Descendants("item")
               on
                   (string)c.Attribute("id")
               equals
                   (string)m.Attribute("CategoryId")
               into menusByCategory
               select new {
                   ID = c.Attribute("id"),
                   CategoryName = c.Element("CategoryName").Value,
                   Menus = from me in menusByCategory select new { Name = me.Element("name").Value }
               };
 
foreach (var c in menuList) {
    Console.WriteLine("{0} - {1}", c.ID, c.CategoryName);
    foreach (var m in c.Menus) {
        Console.WriteLine("\t{0}", m.Name);
    }
}

1,2行目でXML文書を読み込みます。

5行目から今回新しく説明するポイントです。

5行目のjoin句で、4行目のfrom句でcategory.xmlから取り出したxmlのエレメントに結合するmenu.xmlのエレメントを指定します。

6行目からon句から始まり9行目までが結合条件で、この場合にはcategory.xmlがもつidアトリビュートとmenu.xml側が持つ CategoryIdアトリビュートの値の一致をもって結合するようにしています。アトリビュート同士の値が同値であることの確認には"=" (オペレータ)ではなくequals句を使う必要があります。正確には

from <左辺XML>

join <右辺XML>

on <左辺条件> equals <右辺条件>

となります。

10行目ではinto句を使ってcategory.xmlのエレメントが持つidアトリビュートごとにmenu.xmlのエレメントをグループ化して、category.xmlのエレメントに結合します。

14行目ではselectをネストして、グループ化された中からメニューの名前のリストを作成しています。

動作結果

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
d="1" - 飲料
コーラ
アイスコーヒー
ブレンドコーヒー
生ビール
d="2" - 前菜
フレンチフライ
湯で海老
チーズ盛り合わせ
オニオングラタンスープ
シェフのお任せミニサラダ
シェフのお任せスープ
d="3" - 主菜
御殿場高原で育った子羊のロースト
あしたか牛のフィレ肉を使ったビーフシチュー
沼津港スズキのグリル
焼津マグロのカルパッチョ
あしたか牛のサーロインステーキ
d="4" - デザート
韮山苺を使ったショートケーキ
西浦みかんのババロワ
朝霧高原の牛乳から作ったティラミス
d="5" - お持ち帰り
d="1" - 飲料 コーラ アイスコーヒー ブレンドコーヒー 生ビール d="2" - 前菜 フレンチフライ 湯で海老 チーズ盛り合わせ オニオングラタンスープ シェフのお任せミニサラダ シェフのお任せスープ d="3" - 主菜 御殿場高原で育った子羊のロースト あしたか牛のフィレ肉を使ったビーフシチュー 沼津港スズキのグリル 焼津マグロのカルパッチョ あしたか牛のサーロインステーキ d="4" - デザート 韮山苺を使ったショートケーキ 西浦みかんのババロワ 朝霧高原の牛乳から作ったティラミス d="5" - お持ち帰り
d="1" - 飲料
       コーラ
       アイスコーヒー
       ブレンドコーヒー
       生ビール
d="2" - 前菜
       フレンチフライ
       湯で海老
       チーズ盛り合わせ
       オニオングラタンスープ
       シェフのお任せミニサラダ
       シェフのお任せスープ
d="3" - 主菜
       御殿場高原で育った子羊のロースト
       あしたか牛のフィレ肉を使ったビーフシチュー
       沼津港スズキのグリル
       焼津マグロのカルパッチョ
       あしたか牛のサーロインステーキ
d="4" - デザート
       韮山苺を使ったショートケーキ
       西浦みかんのババロワ
       朝霧高原の牛乳から作ったティラミス
d="5" - お持ち帰り

左外部結合

左外部結合は、2 番目のコレクションに相関関係を持つ要素があるかどうかに関係なく、最初のコレクションの各要素が返される結合です。LINQ を使用すると、グループ結合の結果に対して DefaultIfEmpty を呼び出すことで、左外部結合を実行できます。

以下の例は、カテゴリーID、カテゴリー名、メニュー名を表示するプログラムで、カテゴリーに対応するメニューが無くてもカテゴリーIDとカテゴリー名は表示します。

処理の手順は次のようになります。

最初手順ではグループ結合を使用して内部結合を実行します。今回の例ですと、先ほどと同じようにmenu.xmlのエレメントのリストは、 category.xmlのエレメントが持つidアトリビュート と一致する menu.xmlのエレメントに基づいて、category.xmlのエレメントの一覧に内部結合されます。

2 番目の手順では、右側のコレクションに一致する要素がない場合でも、最初 (左側) のコレクションの各要素を結果セットに含めます。これを行うには、グループ結合から一致する要素の各シーケンスで DefaultIfEmpty を呼び出します。以下の例では一致する menu.xmlのエレメントの各シーケンスで DefaultIfEmpty が呼び出されます。一致する menu.xmlのエレメントのシーケンスがすべての category.xmlのエレメントに対して空の場合、各 category.xmlのエレメントが結果コレクションに表示されるように、1 つの既定値を含むコレクションが返されます。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var menuList = from c in category.Descendants("Category")
join m in menu.Descendants("item")
on
(string)c.Attribute("id")
equals
(string)m.Attribute("CategoryId")
into menusByCategory
from mx in menusByCategory.DefaultIfEmpty(
new XElement("item", new XAttribute("id", c.Attribute("id").Value), new XElement("name", ""), new XElement("price", "")))
select new {
ID = c.Attribute("id"),
CategoryName = c.Element("CategoryName").Value,
MenuName = mx.Element("name").Value
};
foreach (var c in menuList) {
Console.WriteLine("{0} - {1} \t:\t{2}", c.ID, c.CategoryName, c.MenuName);
}
var menuList = from c in category.Descendants("Category") join m in menu.Descendants("item") on (string)c.Attribute("id") equals (string)m.Attribute("CategoryId") into menusByCategory from mx in menusByCategory.DefaultIfEmpty( new XElement("item", new XAttribute("id", c.Attribute("id").Value), new XElement("name", ""), new XElement("price", ""))) select new { ID = c.Attribute("id"), CategoryName = c.Element("CategoryName").Value, MenuName = mx.Element("name").Value }; foreach (var c in menuList) { Console.WriteLine("{0} - {1} \t:\t{2}", c.ID, c.CategoryName, c.MenuName); }
var menuList = from c in category.Descendants("Category")
               join m in menu.Descendants("item")
               on
                (string)c.Attribute("id")
               equals
                (string)m.Attribute("CategoryId")
               into menusByCategory
               from mx in menusByCategory.DefaultIfEmpty(
                new XElement("item", new XAttribute("id", c.Attribute("id").Value), new XElement("name", ""), new XElement("price", "")))
               select new {
                   ID = c.Attribute("id"),
                   CategoryName = c.Element("CategoryName").Value,
                   MenuName = mx.Element("name").Value
               };
 
foreach (var c in menuList) {
    Console.WriteLine("{0} - {1} \t:\t{2}", c.ID, c.CategoryName, c.MenuName);
}

11行目はメニューで使われていないカテゴリが表示できるように、DefaultIfEmptyメソッドでmenusByCategory内の各シーケンスを調べ、シーケンスが空でないならシーケンスの各要素をmxに入れ、シーケンスが空であるなら、規定値としてDefaultIfEmptyメソッドの引数で与えている値がすべて空のitemエレメントをmxに入れています。(ここでは値の無いエレメントを追加していますが、それが適切かどうかは場合によります。)

動作結果

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
id="1" - 飲料 : コーラ
id="1" - 飲料 : アイスコーヒー
id="1" - 飲料 : ブレンドコーヒー
id="1" - 飲料 : 生ビール
id="2" - 前菜 : フレンチフライ
id="2" - 前菜 : 湯で海老
id="2" - 前菜 : チーズ盛り合わせ
id="2" - 前菜 : オニオングラタンスープ
id="2" - 前菜 : シェフのお任せミニサラダ
id="2" - 前菜 : シェフのお任せスープ
id="3" - 主菜 : 御殿場高原で育った子羊のロースト
id="3" - 主菜 : あしたか牛のフィレ肉を使ったビーフシチュー
id="3" - 主菜 : 沼津港スズキのグリル
id="3" - 主菜 : 焼津マグロのカルパッチョ
id="3" - 主菜 : あしたか牛のサーロインステーキ
id="4" - デザート : 韮山苺を使ったショートケーキ
id="4" - デザート : 西浦みかんのババロワ
id="4" - デザート : 朝霧高原の牛乳から作ったティラミス
id="5" - お持ち帰り :
id="1" - 飲料 : コーラ id="1" - 飲料 : アイスコーヒー id="1" - 飲料 : ブレンドコーヒー id="1" - 飲料 : 生ビール id="2" - 前菜 : フレンチフライ id="2" - 前菜 : 湯で海老 id="2" - 前菜 : チーズ盛り合わせ id="2" - 前菜 : オニオングラタンスープ id="2" - 前菜 : シェフのお任せミニサラダ id="2" - 前菜 : シェフのお任せスープ id="3" - 主菜 : 御殿場高原で育った子羊のロースト id="3" - 主菜 : あしたか牛のフィレ肉を使ったビーフシチュー id="3" - 主菜 : 沼津港スズキのグリル id="3" - 主菜 : 焼津マグロのカルパッチョ id="3" - 主菜 : あしたか牛のサーロインステーキ id="4" - デザート : 韮山苺を使ったショートケーキ id="4" - デザート : 西浦みかんのババロワ id="4" - デザート : 朝霧高原の牛乳から作ったティラミス id="5" - お持ち帰り :
id="1" - 飲料   :       コーラ
id="1" - 飲料   :       アイスコーヒー
id="1" - 飲料   :       ブレンドコーヒー
id="1" - 飲料   :       生ビール
id="2" - 前菜   :       フレンチフライ
id="2" - 前菜   :       湯で海老
id="2" - 前菜   :       チーズ盛り合わせ
id="2" - 前菜   :       オニオングラタンスープ
id="2" - 前菜   :       シェフのお任せミニサラダ
id="2" - 前菜   :       シェフのお任せスープ
id="3" - 主菜   :       御殿場高原で育った子羊のロースト
id="3" - 主菜   :       あしたか牛のフィレ肉を使ったビーフシチュー
id="3" - 主菜   :       沼津港スズキのグリル
id="3" - 主菜   :       焼津マグロのカルパッチョ
id="3" - 主菜   :       あしたか牛のサーロインステーキ
id="4" - デザート       :       韮山苺を使ったショートケーキ
id="4" - デザート       :       西浦みかんのババロワ
id="4" - デザート       :       朝霧高原の牛乳から作ったティラミス
id="5" - お持ち帰り     :

このように「お持ち帰り」はメニューに存在しませんが表示されています。

ということでひとまず結合を含むクエリ式の説明はこれで終わりです。

それではまた次回!(あるのかー)

これまでの記事:

参考図書:

Essential LINQ (Microsoft .NET Development Series)

Essential LINQ (Microsoft .NET Development Series)

Charlie Calvert

Addison-Wesley Professional 2009-03-22

売り上げランキング : 109833

Amazonで詳しく見る by G-Tools

プログラミングMicrosoft LINQ (マイクロソフト公式解説書 Microsoft Visual Studi)

プログラミングMicrosoft LINQ (マイクロソフト公式解説書 Microsoft Visual Studi)

小高 太郎 (株)オーパス・ワン

日経BPソフトプレス 2009-05-25

売り上げランキング : 43591

Amazonで詳しく見る by G-Tools

コメント

  1. OPC Diary より:

    Linq to XML 入門その1 (XML文書の作り方)

    準備 これから数回に分けてLinq to XMLの基礎的な使い方についてまとめ…

タイトルとURLをコピーしました