到目前為止,您可能已聽說過LINQ(語言集成查詢),它是Visual Studio® 2008中附帶的新查詢技術。啟用LINQ的語言(如Visual Basic®)為您提供了一組豐富的查詢運算符,您可以將這些運算符應用到各種數據源,如內存中的集合、數據庫、數據集和XML。僅這一項技術就已經非常優秀了,但Visual Basic 9.0實際上提供的技術遠不止此,它使XML成為直接集成到語言中的一流數據類型。
現在您可能想知道為什麼需要將 XML 數據類型直接集成到Visual Basic中。當今,許多應用程序都在使用XML進行存儲和數據傳輸。XML由於其靈活性和簡易性而在業內廣泛采用,而且它還用於許多執行存儲和數據傳輸功能的應用程序。由於 XML具有自描述性(即數據的結構包括在數據中),因此它特別適合在系統之間傳輸數據。而且,與為各種自定義文件格式編寫分析規則相比,閱讀在XML標記內部構造的數據要容易得多。
但XML的問題是,開發人員從來沒有非常方便地使用過它。令人費解和不一致的API(如文檔對象模型 (DOM))及語言(如XSLT和XQuery)導致編寫的許多枯燥代碼通常難以閱讀和理解。但在引入LINQ和Visual Basic 9.0之後,XML的開發變得容易多了。在本專欄中,將探討當前的XML 編程體驗,LINQ 如何改進體驗,以及在使用XML時 Visual Basic如何提供更多支持。
使用DOM
首先,假設需要在XML中寫出一個客戶列表。我的客戶列表具有下列屬性:FirstName、LastName、Address、City、State和ZipCode。在將列表轉換為XML時,看上去應如圖1 所示。
Figure1XML中的客戶列表屬性
<Customers>
<Customer FirstName="Jane" LastName="Dow">
<Address>123 Main St</Address>
<City>Redmond</City>
<State>WA</State>
<ZipCode>10104</ZipCode>
</Customer>
<Customer FirstName="Matt" LastName="Berg">
<Address>456 First St</Address>
<City>Seattle</City>
<State>WA</State>
<ZipCode>10028</ZipCode>
</Customer>
</Customers>
使用DOM和Visual Studio 2005,可以編寫代碼遍歷我的列表並構造適當的XML節點,如圖2 所示。您可以看出,對於本來相當簡單的內容也需要編寫許多代碼。而且,由於生成的XML 結構實際上與代碼的結構不匹配,因此很難直觀顯示其結構。
Figure2使用DOM構造XML節點
'With Visual Basic 9, you no longer have to write code like this.
Public Function ConvertToXML(ByVal custList As List(Of Customer)) _
As String
Dim doc As New XmlDocument
Dim root As XmlElement = doc.CreateElement("Customers")
For Each cust As Customer In custList
Dim custElement As XmlElement = doc.CreateElement("Customer")
custElement.SetAttribute("FirstName", cust.FirstName)
custElement.SetAttribute("LastName", cust.LastName)
Dim address As XmlElement = MakeElement(doc, "Address", cust.Address)
Dim city As XmlElement = MakeElement(doc, "City", cust.City)
Dim state As XmlElement = MakeElement(doc, "State", cust.State)
Dim zipcode As XmlElement = MakeElement(doc, "ZipCode", cust.Zip)
With custElement
.AppendChild(address)
.AppendChild(city)
.AppendChild(state)
.AppendChild(zipcode)
End With
root.AppendChild(custElement)
Next
doc.AppendChild(root)
Return doc.InnerXml
End Function
Private Function MakeElement(ByVal doc As XmlDocument, _
ByVal elementName As String, _
ByVal value As String) As XmlElement
Dim element As XmlElement = doc.CreateElement(elementName)
element.InnerText = value
Return element
End Function
從長遠角度看,維護此代碼將會帶來很大的不便。如果業務需求發生變化並且發現需要添加一些屬性或特性,則必須費力地檢查此代碼並確定哪些節點需要更新。
LINQ to XML API
在針對DOM 編碼時,我基本上嘗試從內到外構建XML 樹。這可能必須對付API這一關才能完成我的工作。不必在DOM中構造一大堆節點然後將其連接在一起,如果可以表示 XML的結構然後替換內嵌的值,則操作就會容易得多。這正是通過LINQ to XML API 可以執行的任務,即可以自上而下創建XML(也稱為功能構造)。
使用LINQ,現在我可以編寫圖2中的更簡短更清晰的函數版本。圖3中顯示了新的更為簡單的解決方案。它可以產生與第一個解決方案完全相同的輸出,但可以看出,新的代碼段更簡短且更易於閱讀。我使用XElement和XAttribute對象的構造函數構造了該樹。請注意以下優秀的功能:這些構造函數具有用來傳入其他LINQ to XML對象的重載;例如,我可以將一個XElement 傳入另一個XElement。
Figure3使用LINQ的新解決方案
Public Function ConvertToXML(ByVal custList As List(Of Customer)) As _
String
Dim doc = New XElement("Customers", _
From cust In custList _
Select New XElement("Customer", _
New XAttribute("FirstName", cust.FirstName), _
New XAttribute("LastName", cust.LastName), _
New XElement("Address", cust.Address), _
New XElement("City", cust.City), _
New XElement("State", cust.State), _
New XElement("ZipCode", cust.Zip)))
Return doc.ToString
End Function
第一行創建一個新的XElement,並將其命名為Customers;該行還創建相關的<Customer>和</Customer>標記。傳入構造函數的第二個參數是一個LINQ 查詢,該查詢將成為這些Customer標記中的內容。此查詢遍歷列表中的所有客戶,並將結果轉換為XML。計算 Select語句之後的所有內容,並在運行時生成 Customer 元素的集合。
由於正在查詢數據,因此可以利用LINQ執行篩選、排序、分組、聯接和其他各種操作。例如,可以通過向查詢中添加 Where子句來修改該函數,使之僅返回居住在西雅圖的客戶。如果使用DOM執行此操作,則需要在For 循環中添加 If語句。在LINQ中對結果進行排序現在就像在查詢中添加 Order By子句那樣簡單。使用DOM,需要在構造XML 之前手動對結果進行排序。
這裡使用的示例相當簡單,只是想淺顯地闡明使用LINQ 可以執行哪些操作。到目前為止,已經使用了XElement和XAttribute類型。API 定義了許多其他類型,其中包括 XDocument、XNamespace和XComment。
XML文本和嵌入表達式
盡管LINQ to XML提供了比以前更為簡單的體驗,但還可以進一步簡化此方法。這時便用到了XML文本,它是Visual Basic 9.0中引入的一個新概念。
若要理解 XML文本,必須先理解什麼是文本。請考慮以下變量:
Dim someString = "A string literal"
Dim someNumber = 256
Dim someDate = #2/27/2008#
Dim someBoolean = True
此處顯示的每個變量都直接分配了一個值,對於每種情況,實際值稱為一個文本。(注意,使用Visual Basic 9.0中新的類型推斷功能,即使沒有為每個變量提供“As <Type>”子句,這些聲明仍為強類型。)
引號中的任何內容都代表一個字符串文本。例如,分配給 someDate的值將是一個日期文本。XML文本與此概念完全相同。可以像在任何其他位置表示數據(以其文本表示形式)一樣准確地表示 XML。即兩邊沒有引號;XML文本不是字符串。可以直接將數據分配給 Customers 變量,如圖4 所示,編譯器會將該值視為文本 XML。因此,變量的類型將被推斷為XElement。
Figure4直接將數據分配給 Customers 變量
Dim customers = <Customers>
<Customer FirstName="Jane" LastName="Dow">
<Address>123 Main St</Address>
<City>Redmond</City>
<State>WA</State>
<Zip>10104</Zip>
</Customer>
<Customer FirstName="Matt" LastName="Berg">
<Address>456 First St</Address>
<City>Seattle</City>
<State>WA</State>
<Zip>10028</Zip>
</Customer>
</Customers>
當編譯器看到此XML時,它會將其轉換為一系列的LINQ to XML API調用。這不但更便於閱讀,而且編譯器會真正優化此構造,並且生成代碼的速度比直接使用API要快。
到目前為止,我完成的所有操作就是將一些XML粘貼到項目中,並將其存儲在變量中(但這不一定有什麼用)。有關在Visual Basic 9.0中使用XML支持的一個常見誤解是用它將 XML文檔存儲在代碼中。但事實上遠不是這麼回事。關鍵是提供一個簡單的語法來創建、查詢和轉換XML文檔。我演示了如何使用XML文本方便地創建文檔。但是為了使用這種方法,需要一種辦法將值直接插入XML。這正是嵌入表達式發揮作用的地方。
嵌入表達式實質上告訴編譯器“暫停處理此XML文本,計算表達式的值,並將結果插回 XML”。為了實現這一點,我使用了<%= (expression) %>語法。圖5 采用了ConvertToXml 函數,並演示了如何使用XML文本和嵌入表達式對它進行改進。
Figure5使用XML文本和嵌入表達式
Public Function ConvertToXML(ByVal custList As List(Of Customer)) _
As String
Dim doc = <Customers>
<%= From cust In custList _
Select <Customer FirstName=<%= cust.FirstName %>
LastName=<%= cust.LastName %>>
<Address><%= cust.Address %></Address>
<City><%= cust.City %></City>
<State><%= cust.State %></State>
<ZipCode><%= cust.Zip %></ZipCode>
</Customer> %>
</Customers>
Return doc.ToString
End Function
此示例使用LINQ 查詢生成 XML,但要查詢的數據源實際上是List(Of T)類型的一個內存中的集合。這裡您可以真正了解到LINQ的魅力:我可以對數據庫和其他XML文檔方便地使用與集合相同的查詢運算符,而不必學習每個新的API。並且可以了解到如何從 SQL Server® 數據庫中獲取數據,而且只需要幾行代碼即可方便地將其轉換為XML。
XML屬性
應用程序通常需要檢查 XML(可能通過文件或RSS 源提供),並根據相應數據做出決定。這經常通過XSL 轉換完成,但使用LINQ to XML 則可以省去這種轉換。
在圖6顯示的示例中,我載入了XML文檔,並按居住在西雅圖的客戶篩選,然後選擇名字和姓氏。在此之後,遍歷查詢並在屏幕上顯示所有匹配的結果。
Figure6使用XML軸屬性的簡單查詢
Public Sub DisplaySeattleCustomers()
Dim filePath = My.Application.Info.DirectoryPath & "customers.xml"
Dim doc = XDocument.Load(filePath)
Dim query = From cust In doc...<Customer> _
Where cust.<City>.Value = "Seattle" _
Select Name = cust.@FirstName & " " & cust.@LastName
For Each name In query
MsgBox(name)
Next
End Sub
乍一看,您可能認為這裡好像出了什麼錯。如果編譯器推斷doc為XDocument類型(並且推斷cust為XElement類型),那麼如何鍵入類似 cust.<City>和cust.@FirstName這樣的內容?XElement顯然不具有這些屬性,這是因為它們特定於加載的XML。這裡實際發生的情況是,Visual Basic 編譯器通過一種名為XML屬性的功能節省了大量的工作。
XML屬性有時也稱為XML軸屬性,它提供了一種用於檢索在XML中存儲的值的簡便方法。在圖6顯示的查詢中,我實際上使用了全部三個可用軸:子代、元素和屬性。
在XML中,子代是在當前元素下的一個或多個級別中嵌套的元素。表達式“doc...<Customer>”使用子代軸,它的基本含義是“查找名為Customer的所有子代”。記住,我通篇使用的XML 有一個名為Customers的根節點,而且它還包含 Customer 元素。三節點語法實質上轉換為doc.Descendants("Customer")。
下面我將介紹 cust.<City>.Value 表達式。在本例中,我使用了元素軸,因此該行轉換為cust.Elements("City").Value。在此處進行比較之前,您可能想知道為什麼需要顯式調用.Value(對於屬性不需要此調用)。這是因為像 City這樣的元素可以包含其他元素,因此表達式 cust.<City> 實際上返回 IEnumerable(Of XElement)。.Value 擴展屬性用於連接該元素中存儲的所有值,在本例中只是一個簡單的文本值。
最後一個表達式 cust.@FirstName 使用屬性軸。這將轉換為cust.Attributes("FirstName"),並返回屬性中存儲的值。由於屬性不能包含元素,因此不需要調用.Value。然後使用Select 方法,並將兩個字段合並為一個名為Name的字段,以便更簡單地進行顯示。
XML架構 IntelliSense
在對最後一個示例進行編碼時,如果IntelliSense®能告訴我XML文檔中字段的名稱就太好了。但問題是,編譯器如何知道XML文檔的結構?答案是通過XSD架構。如果有一個XML架構,則可以在Visual Basic中使用Imports語句將該架構引入作用域。在執行此操作後,就會獲得基於您的架構的真正 IntelliSense。
使用Visual Basic 2008的XML to Schema工具(可以在go.microsoft.com/fwlink/?LinkId=102501 上下載),您可以根據現有的XML文件快速創建XSD架構。只需要在XML文件或Web URL中指向該架構,它就會執行生成 XSD文件的單調工作。
結束語
Visual Basic 是專門為提高工作效率而設計的。隨著企業應用程序在存儲、數據轉換甚至用戶界面方面越來越依賴於 XML,Visual Basic 合並了一些便於開發人員可以更好地使用XML的功能,這一點很重要。Visual Basic 9.0 在這方面取得了較大的進步,提供了豐富的XML支持和強大的新查詢功能。
使用LINQ提供的簡單而強大的查詢語法,您可以針對XML 編寫查詢,這與針對數據庫或內存中的集合編寫查詢一樣方便。而且,在開始體驗 Visual Basic 9.0時,你會發現許多本文沒有介紹到的XML 新功能,包括對XML 命名空間、注釋和片段的內置支持。