LINQ to XML – Elements()與Descendants()的使用差異
剛開始接觸LINQ的人一定會覺得他的寫法好像在寫SQL語法一樣,有SELECT、FROM、WHERE等子句,
這樣的語法讓我們很容易的去透過直覺的方式將需要的資料給搜尋出來。然而,LINQ並非只用於用熟悉
的Entity Framework,它是一種資料存取標準,因此,操作的資料是屬於IEnumerable介面下的物件才有辦
法使用(可參考<LINQ 與 IEnumerable 介面>)。
然而LINQ本身又可以根據資料來源對象分成:LINQ to Objects、LINQ to XML、LINQ to ADO.NET與最新的
LINQ to Emtities。這些API的內容都可以協助我們操作與撰寫程式,但其實在Debug的時候其實就不是這麼
容易,其中以LINQ to XML的Debug是最複雜的情形。
舉個例子來說:
今天有一個XML內容如下:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <Employees>
3: <Employee>
4: <ID>0001</ID>
5: <Name>pou</Name>
6: <BasicInfo>
7: <Cellphone>0932133456</Cellphone>
8: <Email>pou@dd.com</Email>
9: <Home>(02)2262123</Home>
10: </BasicInfo>
11: <Salary>NT$ 100,000</Salary>
12: </Employee>
13: <Employee>
14: <ID>0002</ID>
15: <Name>pou2</Name>
16: <BasicInfo>
17: <Cellphone>0932133456</Cellphone>
18: <Email>pou2@dd.com</Email>
19: <Home>(02)2262123</Home>
20: </BasicInfo>
21: <Salary>NT$ 100,000</Salary>
22: </Employee>
23: <Employee>
24: <ID>0003</ID>
25: <Name>pou3</Name>
26: <BasicInfo>
27: <Cellphone>0932133456</Cellphone>
28: <Email>pou3@dd.com</Email>
29: <Home>(02)2262123</Home>
30: </BasicInfo>
31: <Salary>NT$ 100,000</Salary>
32: </Employee>
33: <Employee>
34: <ID>0004</ID>
35: <Name>pou4</Name>
36: <BasicInfo>
37: <Cellphone>0932133456</Cellphone>
38: <Email>pou@dd.com</Email>
39: <Home>(02)2262123</Home>
40: </BasicInfo>
41: <Salary>NT$ 100,000</Salary>
42: </Employee>
43: </Employees>
你通常會使用Elements()來取得root下的所有Elements,但你會發現結果出現:「產生的列舉型號沒有結果」,如下圖:
然而,如果在使用Elements()之前多加入了一個Descendants()後,就能正確取得Elements,到底是怎麼一回事呢?
首先,我們先釐清一些基本的觀念:
(1) XDocument把XML內容讀入之後,使用Element()就能取得目前根元素下的所有符合的Elements?
=>這個是錯誤的觀念,因為會有這樣的觀念是常寫一些XPath的人員會比較常遇到的問題,像我一開始學習時候就是如此。
因此,要怎麼修改呢?當我們整份XML讀入時,該份XML被轉換成一個集合,root或elements都被合在一起,因此,需要
需過XDocument中的Root屬性來分割開root與Elements,程式碼如下:
(2) 那為何使用Dscendants()就能取得與(1)相同的結果?二者之間的差異在那裡?
舉個例子來說,假設我修改上述的XML如下:(在Employee為pou2之中加入<New>的Tag內含有<Employee>標籤)
那到底取出來會有多少個Employee呢?
1: <?xml version="1.0" encoding="utf-8" ?>
2: <Employees>
3: <Employee>
4: <ID>0001</ID>
5: <Name>pou</Name>
6: <BasicInfo>
7: <Cellphone>0932133456</Cellphone>
8: <Email>pou@dd.com</Email>
9: <Home>(02)2262123</Home>
10: </BasicInfo>
11: <Salary>NT$ 100,000</Salary>
12: </Employee>
13: <Employee>
14: <ID>0002</ID>
15: <Name>pou2</Name>
16: <BasicInfo>
17: <Cellphone>0932133456</Cellphone>
18: <Email>pou2@dd.com</Email>
19: <Home>(02)2262123</Home>
20: </BasicInfo>
21: <Salary>NT$ 100,000</Salary>
22: <New>
23: <Employee>
24: <ID>0020</ID>
25: <Name>pou20</Name>
26: </Employee>
27: </New>
28: </Employee>
29: </Employees>
可以看到使用Descendants()取出的結果比Root.Elements()多了一個,多的這一個正是我們寫入在<Employee>中<New>的
新標籤,由此可見,透過Descendants()它會將存在於該XDocument中所有符合"Employee"的內容全部找出來(類例把XML
每一個標籤全部平放一次搜尋);而Elements()則是指定於Root下(或其他父集合下)的指定的Elements()擷取出來,屬於往
下一層搜尋,而不像Descendants()把所有的Tag全部拉成齊頭平等來搜尋。
另外,從MSDN上針對二者的說明,也可以清楚的了解二者之間的差異:
‧Elements<T>(IEnumerable<T>):傳回來源集合中每個項目和文件的子項目集合。
‧Descendants<T>(IEnumerable<T>):傳回包含來源集合中每個項目和文件之子代項目的項目集合。
上述的解釋可以讓我們清楚二者之間的差異,雖然目前很少會遇到XML內容中相同的Tag同時用於父集合或子項目中,
但Descendants()的用法是需要特別注意的,避免想要取出的結果與預期的不相同。
[補充]
以上的例子是透過XDocument取得原始的XML內容,如果今天使用的是直接操作XElement,那你不會看到XElement中出
現Root的屬性,此時的操作就可以直接使用Elements()的方法來使用,而不需要加入Root.Elements()來取得該XElement中
的子項目內容。如下之範例:
以上是分享自己在剛學習寫LINQ to XML遇到的實際問題,希望對自我修練的人員有助釐清一些
使用方法的概念與原理。
References:
‧How to: Debug Empty Query Results Sets
‧http://msdn.microsoft.com/en-us/library/bb385795.aspx
‧http://blogs.msdn.com/b/ericwhite/archive/2008/11/07/debugging-linq-queries.aspx
‧Using LINQ to XML (and how to build a custom RSS Feed Reader with it)
‧Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)