LINQ to XML Programming Using Visual Basic.NET 2008
This article mainly focuses on manipulating XML documents using LINQ to XML, available in .NET Framework 3.5. In simple terms, we will design a Windows Form and do all CRUD operations against an existing XML document using LINQ to XML. We will also see how to use XPath in LINQ to XML.
To make the article simple, I designed a Windows Form as follows.
The following is the structure of the XML being followed in this article.
The explanation for the above XML (and basics of XML DOM/API in .NET Framework) is explained in my previous article titled "Visual Basic 2005 XML Programming using XML DOM" along with a sample XML document. To keep this article to the point, I will not repeat the same explanation here.
The entire source code for this article is available in the form of a free downloadable zip file. The solution was developed using Microsoft Visual Studio 2008 on Microsoft Windows Server 2003 Standard Edition (with SP3). I didn't really test it in any other environment. I request that you post in the discussion area if you have any problems in execution.
The starting point to learn LINQ to XML programming is to load the XML document into an object. Let us look at that:
Const XMLDOCFILEPATH As String = "....MyData.xml"
Dim doc As XDocument
doc = XDocument.Load(XMLDOCFILEPATH)
In the above code, I am creating a new XDocument object and loading XML into it. The XDocument class is completely different from the XMLDocument class. XDocument belongs to the "System.Linq" family whereas "XMLDocument" belongs to the "System.XML" family. Make sure that you import "System.Linq" before you work on "LINQ to XML" related classes.
Once the XML is loaded into the "XDocument" object, we can mold it to any kind of strongly typed object collection, simply by using "LINQ to XML Queries." For instance, the following would parse through all of the XML and give out a "List" based strongly typed collection with exactly the structure we need (Employee Information in this case):
Dim qList = From xe In doc.Descendants.Elements("Employee") _
Select New With { _
.ID = xe.Attribute("ID").Value, _
.Empno = xe.Element("Empno").Value, _
.Ename = xe.Element("Ename").Value, _
.Sal = xe.Element("Sal").Value, _
.Deptno = xe.Element("Deptno").Value _
}
Me.DataGridView1.DataSource = qList.ToList
If you observe the above code snippet, it is pretty similar to SQL querying. However, the above queries an XML Document!
Let us consider finding information on a single employee based on the employee number provided. The following is the code:
Dim qEmp = (From xe In doc.Descendants.Elements("Employee") _
Where xe.<Empno>.Value = "1010" _
Select New With { _
.ID = xe.@ID, _
.Empno = xe.<Empno>.Value, _
.Ename = xe.<Ename>.Value, _
.Sal = xe.<Sal>.Value, _
.Deptno = xe.<Deptno>.Value _
}).FirstOrDefault
In the above code, I wrote a different kind of query which directly uses the XML kind of syntax in Visual Basic itself. This is another great possibility in Visual Basic 2008. You can also observe that I injected a "Where" clause in the "From" statement.
Once the employee is found, we can retrieve information on its child elements as follows:
In previous section, we saw how to retrieve and search an XML document using "LINQ to XML." In this section we will manipulate an XML document with Add, Update and Delete operations.
Let us consider adding a new employee to department 30. The following is the sample code:
Dim qTargetDept As XElement = doc.Descendants.Elements("Department") _
.Where(Function(xe) xe.<Deptno>.Value = "30") _
.FirstOrDefault
If qTargetDept Is Nothing Then
MessageBox.Show("Department/EmployeeInfo not found")
Exit Sub
End If
Dim oEmp As New XElement("Employee")
oEmp.Add(New XAttribute("ID", "E20"))
oEmp.Add(New XElement("Empno", "4001"))
oEmp.Add(New XElement("Ename", "Winner"))
oEmp.Add(New XElement("Sal", "4500"))
oEmp.Add(New XElement("Deptno", "30"))
qTargetDept.Element("EmployeeInfo").Add(oEmp)
In the above code snippet, I am using the "XElement" class instead of the "XMLElement" class. "XElement" belongs to "System.Linq."
Similarly, we can also modify existing records using the following code:
Dim qEmp As XElement = doc.Descendants.Elements("Employee") _
.Where(Function(xe) xe.<Empno>.Value = "4001") _
.FirstOrDefault
qEmp.<Ename>.Value = "Winner2"
qEmp.<Sal>.Value = "5400"
qEmp.<Deptno>.Value = "30" 'this is not necessary
qEmp.@ID = "E40"
In the same manner, we can use the following code to delete an existing record:
Dim qEmp As XElement = doc.Descendants.Elements("Employee") _
.Where(Function(xe) xe.<Empno>.Value = "4001") _
.FirstOrDefault
qEmp.Remove()
Finally, we can save the XML document back to the file using the following code:
doc.Save(XMLDOCFILEPATH)
Make sure that "Save" is still supported in "XDocument" and it works in the same manner as "XMLDocument.Save" does.
To demonstrate all of the previous concepts, I designed a Windows form for CRUD operations as shown in Fig 1. The following is the code developed for the form:
Imports System.Linq
Public Class Form1
Const XMLDOCFILEPATH As String = "....MyData.xml"
Dim doc As XDocument
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
RefreshXDoc()
End Sub
Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRefresh.Click
Dim qList = From xe In doc.Descendants.Elements("Employee") _
Select New With { _
.ID = xe.Attribute("ID").Value, _
.Empno = xe.Element("Empno").Value, _
.Ename = xe.Element("Ename").Value, _
.Sal = xe.Element("Sal").Value, _
.Deptno = xe.Element("Deptno").Value _
}
Me.DataGridView1.DataSource = qList.ToList
End Sub
Private Sub btnSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSearch.Click
Dim qEmp = (From xe In doc.Descendants.Elements("Employee") _
Where xe.<Empno>.Value = Me.txtEmpno.Text _
Select New With { _
.ID = xe.@ID, _
.Empno = xe.<Empno>.Value, _
.Ename = xe.<Ename>.Value, _
.Sal = xe.<Sal>.Value, _
.Deptno = xe.<Deptno>.Value _
}).FirstOrDefault
If qEmp Is Nothing Then
MessageBox.Show("Not found")
Exit Sub
End If
Me.txtEname.Text = qEmp.Ename
Me.txtSal.Text = qEmp.Sal
Me.txtDeptno.Text = qEmp.Deptno
Me.txtID.Text = qEmp.ID
End Sub
Private Sub btnClear_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClear.Click
For Each c As Control In Me.Controls
If TypeOf c Is TextBox Then
CType(c, TextBox).Text = ""
End If
Next
End Sub
Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAdd.Click
Dim qTargetDept As XElement = doc.Descendants.Elements("Department") _
Most developers are familiar and comfortable with XPath. However, "LINQ to XML" does not support "XPath" directly. No method in "LINQ to XML" directly supports "XPath." However, Microsoft has provided an alternative way to use "XPath" expressions in "LINQ to XML" using "Extension Methods."
Let us consider that I would like to find all "Employee" elements in the document using XPath and want to use "LINQ to XML" to convert the result to strongly typed objects. This can be easily achieved using the following code:
Imports System.Linq
Imports System.Xml.Linq
Imports System.Xml.XPath.Extensions
Public Class Form2
Const XMLDOCFILEPATH As String = "....MyData.xml"
Private Sub btnRefresh_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnRefresh.Click
Dim doc As XDocument = XDocument.Load(XMLDOCFILEPATH)
Dim qList = From xe In doc.XPathSelectElements("//Employee") _
Select New With { _
.ID = xe.Attribute("ID").Value, _
.Empno = xe.Element("Empno").Value, _
.Ename = xe.Element("Ename").Value, _
.Sal = xe.Element("Sal").Value, _
.Deptno = xe.Element("Deptno").Value _
}
Me.DataGridView1.DataSource = qList.ToList
End Sub
End Class
You can also find a single employee information using XPath together with "LINQ to XML" as follows:
Private Sub btnSingleEmployee_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSingleEmployee.Click
Dim doc As XDocument = XDocument.Load(XMLDOCFILEPATH)
Dim qEmp = (From xe In doc.XPathSelectElements(String.Format("//Employee[Empno='{0}']", Me.txtEmpno.Text)) _