<?xml version="1.0" encoding="utf-8" ?>
<XExtract xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Config.xsd">
<Job Id="Bank">
<Connection Type="Ldap" Vendor="GenericLdap2">
<Address>0</Address>
<Password>0</Password>
<Port>0</Port>
<UserName>0</UserName>
</Connection>
<Connection Kind="Target" Type="Database" Vendor="IbmDb28">
<Address>0</Address>
<Password>0</Password>
<Port>0</Port>
<UserName></UserName>
</Connection>
</Job>
</XExtract>
Parsing the example XML above in LINQ To XML is pretty simple. But what if my XML schema allows optional elements or attributes? If I am not too careful, a non existing element or attribute can cause the LINQ provider to throw an exception. For example, consider the following code segment used to parse the example XML above:
XJob[ ] Jobs = ( from Job in Root.Descendants( "Job" ) select new XJob {
Connections = ( from Connection in Job.Descendants( "Connection" ) select new XConnection {
Address = Connection.Element( "Address" ).Value ,
Kind = (XConnectionKind)Enum.Parse( typeof( XConnectionKind ) , Connection.Attribute( "Kind" ).Value ) ,
Password = Connection.Element( "Password" ).Value ,
Port = ushort.Parse( Connection.Element( "Port" ).Value ) ,
Provider = Connection.Element( "Provider" ).Value ,
Type = (XConnectionType)Enum.Parse( typeof( XConnectionType ) , Connection.Attribute( "Type" ).Value ) ,
UserName = Connection.Element( "UserName" ).Value ,
Vendor = (XConnectionVendor)Enum.Parse( typeof( XConnectionVendor ) , Connection.Attribute( "Vendor" ).Value )
} ).ToArray( )
} ).ToArray( );
The most important part of the above code segment are the parts where I get the value of either a specific element or a specific attribute, as follows:
Address = Connection.Element( "Address" ).Value
The XContainer.Element( string ) Method returns an XElement Object representing the element I want. So above, by invoking Connection.Element( "Address" ), I am returning an Object representing an element named Address in my DOM.
The Value property returns the actual value of the element. So again, referencing the example XML, the above code segment would return 0.
Okay, so what about optional elements? Assume that the Address element above is optional, meaning it can either be there or not. When I am parsing my XML, the following code segment will throw an exception at run time if the Address element is omitted, remember it is optional so it is legal for it to be omitted:
// Throws an exception if the Address element does not exist.
Address = Connection.Element( "Address" ).Value
Why will it throw an exception? Remember that the Connection.Element( "Address" ) Method returns an Object representing the element I want. If the element does not exist, it will return a Null Reference (null). Since invoking the Value Property on a null reference is an illegal operation, an exception is thrown!
So how do I work around this problem without messing up my code? I can litter it with conditional statements, but that is asking for a headache the next time I review my code. Fortunately, the LINQ To XML provider provides explicit type casting ob XElement Objects.
What this means is that I can actually type cast an entire XElement Object without worrying about whether or not it is a Null Reference. So, let's take a look at my updated code segment to get the Address element:
// Will NOT throw an exception if the Address element does not exist.
Address = (string)Connection.Element( "Address" )
Now let's update our LINQ query to compare the difference:
XJob[ ] Jobs = ( from Job in Root.Descendants( "Job" ) select new XJob {
Connections = ( from Connection in Job.Descendants( "Connection" ) select new XConnection {
Address = (string)Connection.Element( "Address" ) ,
Kind = (XConnectionKind)Enum.Parse( typeof( XConnectionKind ) , (string)Connection.Attribute( "Kind" ) ) ,
Password = (string)Connection.Element( "Password" ) ,
Port = ushort.Parse( (string)Connection.Element( "Port" ) ) ,
Provider = (string)Connection.Element( "Provider" ) ,
Type = (XConnectionType)Enum.Parse( typeof( XConnectionType ) , (string)Connection.Attribute( "Type" ) ) ,
UserName = (string)Connection.Element( "UserName" ) ,
Vendor = (XConnectionVendor)Enum.Parse( typeof( XConnectionVendor ) , (string)Connection.Attribute( "Vendor" ) )
} ).ToArray( )
} ).ToArray( );
As you can see, the LINQ query is not only safe to use now, but it also still retains its format without being littered with conditional statements.
No comments:
Post a Comment
Feel free to write any comments or ideas!