How to get the value of an XML element using Linq even when empty
Please excuse my stupidity, I tend to find the traversing XML overly complicated.
I am using ASP.NE开发者_运维问答T in VB.
I have an XML document which contains all the details of staff in my company...
<staff>
<staffName>Test Staff</staffName>
<staffTitle>Slave</staffTitle>
<staffDepartmentName>Finance</staffDepartmentName>
<staffOffice>London</staffOffice>
<staffEmail>t.staff@company.co.uk</staffEmail>
<staffPhone>0207 123 456</staffPhone>
<staffNotes>Working hours Mon to Thurs 9.15 - 5.15</staffNotes>
<staffBio></staffBio>
</staff>
As you can see, some nodes do not always contain data for ever member of staff; only Directors have biographies.
I access the values like this...
For Each staff In ( _
From matches In myXMLFile.Descendants("staff").Descendants("staffName") _
Where matches.Nodes(0).ToString.ToLower.Contains(LCase(search)) _
Order By matches.Value _
Select matches)
staffName = staff.Descendants("staffName").Nodes(0).ToString)
staffTitle = staff.Descendants("staffTitle").Nodes(0).ToString)
staffOffice = staff.Descendants("staffOffice").Nodes(0).ToString)
staffEmail = staff.Descendants("staffEmail").Nodes(0).ToString)
staffPhone = staff.Descendants("staffPhone").Nodes(0).ToString)
staffNotes = staff.Descendants("staffNotes").Nodes(0).ToString)
staffBio = staff.Descendants("staffBio").Nodes(0).ToString)
' Do something with that data...
Next
Once it gets to staffBio I get an error saying "Object reference not set to an instance of an object." obviously because that node does not exist.
My question is how can I assign the value to a variable even when it is empty without having to do a conditional check before each assignment?
First, myXMLFile.Descendants("staff").Descendants("staffName")
is redundant. Descendants returns all the elements at any level within an XDocument
or XElement
. So, myXMLFile.Descendants("staffName")
would give the same result.
Second, you can just use the Element property and the Value property like this:
staffBio = staff.Element("staffBio").Value
staff
will only have one staffBio
element, so there's no need to use the Descendants property. Value
is a string, so you don't need to call Value.ToString
. If the element is empty, then Value will return an empty string, which is what you're looking for!
Third, there is a much better (and I believe more straight forward) way of doing this in VB.NET. Here's a console app that demonstrates how I would do this:
Module Module1
Sub Main()
Dim myXMLFile = <allStaff>
<staff>
<staffName>Test Staff</staffName>
<staffTitle>Slave</staffTitle>
<staffDepartmentName>Finance</staffDepartmentName>
<staffOffice>London</staffOffice>
<staffEmail>t.staff@battens.co.uk</staffEmail>
<staffPhone>0207 123 456</staffPhone>
<staffNotes>Working hours Mon to Thurs 9.15 - 5.15</staffNotes>
<staffBio></staffBio>
</staff>
<staff>
<staffName>Other Staff</staffName>
<staffTitle>Master</staffTitle>
<staffDepartmentName>IT</staffDepartmentName>
<staffOffice>Oxford</staffOffice>
<staffEmail>o.staff@battens.co.uk</staffEmail>
<staffPhone>0207 123 789</staffPhone>
<staffNotes></staffNotes>
<staffBio>Some guy.</staffBio>
</staff>
</allStaff>
Dim search = "Test"
Dim searchQuery = From staff In myXMLFile...<staff> _
Where staff.<staffName>.Value.Contains(search) _
Select si = New StaffInfo With {.Name = staff.<staffName>.Value, _
.Title = staff.<staffTitle>.Value, _
.Department = staff.<staffDepartmentName>.Value, _
.Office = staff.<staffOffice>.Value, _
.Email = staff.<staffEmail>.Value, _
.Phone = staff.<staffPhone>.Value, _
.Notes = staff.<staffNotes>.Value, _
.Bio = staff.<staffBio>.Value}
For Each staff In searchQuery
Console.WriteLine("Name: {0}", staff.Name)
Console.WriteLine("Title: {0}", staff.Title)
Console.WriteLine("Department: {0}", staff.Department)
Console.WriteLine("Office: {0}", staff.Office)
Console.WriteLine("Email: {0}", staff.Email)
Console.WriteLine("Phone: {0}", staff.Phone)
Console.WriteLine("Notes: {0}", staff.Notes)
Console.WriteLine("Bio: {0}", staff.Bio)
Console.WriteLine()
Next
Console.ReadLine()
End Sub
Private Class StaffInfo
Private _name As String
Public Property Name() As String
Get
Return _name
End Get
Set(ByVal value As String)
_name = value
End Set
End Property
Private _title As String
Public Property Title() As String
Get
Return _title
End Get
Set(ByVal value As String)
_title = value
End Set
End Property
Private _department As String
Public Property Department() As String
Get
Return _department
End Get
Set(ByVal value As String)
_department = value
End Set
End Property
Private _office As String
Public Property Office() As String
Get
Return _office
End Get
Set(ByVal value As String)
_office = value
End Set
End Property
Private _email As String
Public Property Email() As String
Get
Return _email
End Get
Set(ByVal value As String)
_email = value
End Set
End Property
Private _phone As String
Public Property Phone() As String
Get
Return _phone
End Get
Set(ByVal value As String)
_phone = value
End Set
End Property
Private _notes As String
Public Property Notes() As String
Get
Return _notes
End Get
Set(ByVal value As String)
_notes = value
End Set
End Property
Private _bio As String
Public Property Bio() As String
Get
Return _bio
End Get
Set(ByVal value As String)
_bio = value
End Set
End Property
End Class
End Module
If you have a schema (.xsd file) for your XML, then you can import a reference to that xmlns to your VB source file, which will give you intellisense for writing your LINQ to XML queries.
(Edit: A quick way to create a schema is to open up an XML file in Visual Studio and choose Create Schema from the XML menu.)
For more information and help, please check out the "How Do I" video series on LINQ by Beth Massi.
OK think this is how to do it.
...
staffBio = staff.Descendants("staffBio").ElementAtOrDefault(0).Value.ToString)
...
Using .ElementAtOrDefault(0)
instead of .Nodes(0)
just returns "" if it's empty or <staffBio>whatever</staffBio>
if it is not.
.Value just returns the content of the tags like "whatever" in the example above.
Is this right? Can anyone see any problems with this?
精彩评论