Browse DevX
Sign up for e-mail newsletters from DevX


Sharpening Your Axis with Visual Basic 9 : Page 5

Visual Basic 9 completely eliminates the barrier between the code you write and the XML you're trying to express. Creating, querying, and transforming XML is much more intuitive and productive than ever before.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Creating Relational Data from XML
As you can imagine, it's also very easy to create relational data by reading XML. You can easily navigate XML in Visual Basic using XML axis properties. For example, suppose you want to read an XML document called Customers.xml that contains customers in the United States—like the one you created in the first Northwind example. It's a good idea to first add an XML namespace to the Customers.xml document. That will make it much easier if you need to work with multiple namespaces and schemas in the project later.

<?xml version="1.0" encoding="utf-8"?> <customers xmlns= "urn:microsoft:examples:customers"> <customer id="GREAL"> <name>Howard Snyder</name> <address>2732 Baker Blvd.</address> <city>Eugene</city> <zip>97403</zip> </customer> . . . </customers>

Next, infer the schema in order to enable XML IntelliSense using the XML to Schema tool. Add a new item to the project, select the XML to Schema template, and name it Customers.xsd. Then specify the location of the Customers.xml by selecting "Add from file…" and browsing to the file location; then click OK. That will infer the schema and add it to your project all in one step.

Import the XML namespace at the top of your code file:

Imports <xmlns:customers= "urn:microsoft:examples:customers">

Now, when you write LINQ to XML queries over this customer document you will get full IntelliSense on the XML axis properties. For example, to select all the IDs and names of the customers that live in cities that start with the letter "P," you would write:

Dim custXML = XElement.Load("Customers.xml") Dim CustomersLikeP = _ From Customer In custXML.<customers:customer> _ Where Customer.<customers:city>.Value Like "P*" _ Select Customer.@id, _ Customer.<customers:name>.Value For Each cust In CustomersLikeP Console.WriteLine(cust.id & ": " & cust.name) Next

This query produces a collection of anonymous types with two properties, id and name, created based on the names of the element and the attribute that you selected from the XML. Anonymous types are another new feature of VB9 that allow you to project unnamed types from LINQ queries. This allows you to use the results from your query immediately, without having to create a class to hold the results.

The next example reads the XML document and adds any customers that are not already in the Northwind database. You can write a single LINQ to XML query to do this. To select only the customers in the XML document where there is no corresponding customer in the database, the example uses the Group Join syntax to "outer join" the customers in the XML document to the customers in the database, selecting only those where the count is zero (meaning there is no corresponding row in the database). Additionally, the code selects a set of new LINQ to SQL Customer objects to create a collection of Customer objects that can be attached to the DataContext and automatically inserted into the database. Remember, the O/R designer created the Customer class when you added the Customers table to the LINQ to SQL model as described in the first Northwind example. Refer back to Figure 11 if you need to:

Dim db As New NorthwindDataContext Dim customersXML = XElement.Load("Customers.xml") Dim newCustomers = _ From Cust In customersXML.<customers:customer> _ Group Join CurrentCust In db.Customers _ On Cust.@id Equals CurrentCust.CustomerID _ Into Count() _ Where Count = 0 _ Select New Customer With _ {.CustomerID = Cust.@id, _ .CompanyName = "New Company", _ .ContactName = Cust.<customers:name>.Value, _ .Address = Cust.<customers:address>.Value, _ .City = Cust.<customers:city>.Value} db.Customers.InsertAllOnSubmit(newCustomers) db.SubmitChanges()

As you type this query into the editor you'll get full IntelliSense on the customer XML axis properties. Note that the New Customer With {... syntax is another new feature in VB9 called object initializers that let you both create objects and assign their properties in one line of code. Also note that this code loads the document into an XElement, and finds the level where the <customer> elements are by using the child axis, which is faster than using the descendants axis. However, if you need to query documents where you do not know for sure on what level the <customer> element resides, you would want to use the descendants axis like this: customersXML...<customers:customer>.

The preceding query produces a collection of new Customer objects. You add this collection to the DataContext by calling the InsertAllOnSubmit method, and then insert the new customers into the database by calling SubmitChanges. For more information on LINQ to SQL, see the LINQ to SQL documentation on MSDN.

As you can see, it's extremely easy to query XML using LINQ and XML axis properties, especially if you enable XML IntelliSense. The possibilities are rather endless.

Advanced Namespace Scenarios
Now that you've seen how to create, transform, and query XML using XML literals, embedded expressions, and axis properties, the rest of this article presents some more advanced scenarios around XML namespaces.

The Default XML Namespace
The default XML namespace can be extremely useful when creating an XML document using many functions and literal fragments. When using the default XML namespace you do not need to prefix the elements in all the literals; you can simply define the default namespace at the top of the code file and all the elements in that file will be in that namespace.

If you don't define a default namespace, the default XML namespace in Visual Basic is the empty string. There are three places in your applications where you can override this default and define your own; inside the literal, in the Imports section at the top of the code file, or in the Imports section for the project. The important thing to remember when defining a default XML namespace is that the file and the project default namespaces apply to both the XML properties and XML literals but do not apply to attributes.

For example, the following Imports statement sets urn::myNameSpace as the default XML namespace for the file:

Imports <xmlns="urn::myNamespace">

Now you can create an XML document in this namespace as follows:

Dim doc = <a> <b> <c></c> </b> </a> <a xmlns="urn::myNamespace"> <b> <c></c> </b> </a>

Figure 14. Default Namespace Recognized: XML properties pick up the default namespace, so you'll see applicable elements included in the IntelliSense list.
Because the XML properties also pick up this namespace you can observe that the applicable elements are included in the IntelliSense list (see Figure 14). You can also override the default namespace inside the XML literal itself. For example:

Dim doc = <a> <b xmlns= "urn::AnotherDefaultNamespace"> <c></c> </b> </a>

That code produces the following XML document:

<a xmlns="urn::myNamesapce"> <b xmlns="urn::AnotherDefaultNamespace"> <c></c> </b> </a>

To access the <b> and <c> elements using the XML properties, you'll need to add another Imports statement and give the other namespace an alias. This is because you can declare only one default namespace at the file level:

Imports <xmlns:ns="urn::AnotherDefaultNamespace">

You can see the result in Figure 15.

Figure 15. Imported Namespaces: XML IntelliSense displays Imported XML Namespace aliases.
Reify XML Namespace
You may encounter cases when you need to get the actual namespace that was declared in the Imports statement as an XNamespace object, another object in the LINQ to XML API. For example, suppose you need to create the name of the XML literal element using an embedded expression. The built-in function GetXmlNamespace makes this simple. For example, assume you have the following Imports declarations:

Imports <xmlns="urn::myNamesapce"> Imports <xmlns:ns="urn::AnotherDefaultNamespace">

When you write this code:

Console.WriteLine(GetXmlNamespace()) Console.WriteLine(GetXmlNamespace(ns))

It produces the following results:

urn::myNamesapce urn::AnotherDefaultNamespace

The next example uses this built-in function when calling the LINQ to XML Ancestors() method, which returns all the ancestor "{urn::AnotherDefaultNamespace}customer" elements of the order element:

Dim customers = _ order.Ancestors( _ GetXmlNamespace(ns).GetName("customer"))

Bubbling and Removing Redundant Namespace Declarations
Figure 16. Bubbling and Removing Redundant Namespace Declarations: The figure shows the VB9 compiler's decision tree for bubbling and removing redundant namespace declarations.
XML documents created from multiple functions and embedded LINQ queries typically have many redundant XML namespace declarations, because each element created on its own has to have the full namespace information contained within that element. However when this element becomes part of another element, the namespaces might be declared on the parent element, so they become redundant on the child elements. The VB9 compiler removes the redundant namespaces when the document gets assembled from XML literals during compilation and also at runtime. Figure 16 shows a flowchart of this process.

A few rules apply to this process. Only the global namespaces defined using the Imports statement, and local declarations that match global declarations get moved up to the parent element, creating the bubbling effect. In this case "match" means both the prefix and the namespace itself. Within a literal, namespaces will bubble up at compile time. However, if you add an element to another element by calling a function inside an embedded expression, the namespaces will bubble up at runtime.

The following examples show how this process works. Here's a simple example of bubbling the namespace to the top level:

Imports <xmlns:a="http://mynamespace1"> ... Dim doc = <level0> <level1> <a:level2></a:level2> </level1> </level0>

When you output doc, you'll get the following results:

<level0 xmlns:a="http://mynamespace1"> <level1> <a:level2></a:level2> </level1> </level0>

In the next example the declaration already exists on the parent element. Note that because it's the same prefix and same namespace, everything moves to the root element:

Imports <xmlns:a="http://mynamespace1"> ... Dim doc2 = <level0> <level1 xmlns:a= "http://mynamespace1"> <a:level2></a:level2> </level1> </level0>

The outcome is that when you output doc2, you'll get the same results as before:

<level0 xmlns:a="http://mynamespace1"> <level1> <a:level2></a:level2> </level1> </level0>

Local declarations do not bubble up unless the local declaration matches a global one. In this case you don't declare <b> globally:

Imports <xmlns:a="http://mynamespace1"> ... Dim doc3 = <level0> <b:level1 xmlns:b= "http://Othernamespace"> <a:level2></a:level2> </b:level1> </level0>

So now when you output doc3, you'll see this:

<level0 xmlns:a="http://mynamespace1"> <b:level1 xmlns:b="http://Othernamespace"> <a:level2></a:level2> </b:level1> </level0>

The prefix and namespace must match to bubble up; if they don't, then no bubbling occurs. Notice what happens when the declaration exists but with a different prefix:

Imports <xmlns:a="http://mynamespace1"> ... Dim doc4 = <level0> <b:level1 xmlns:b= "http://mynamespace1"> <a:level2></a:level2> </b:level1> </level0>

You'll end up with this result when you output doc4:

<level0 xmlns:a="http://mynamespace1"> <b:level1 xmlns:b="http://mynamespace1"> <b:level2></b:level2> </b:level1> </level0>

Namespace bubbling also happens when working with embedded expressions. In the following example, the bubbling happens at compile time:

Imports <xmlns:a="http://mynamespace1"> ... Dim doc5 = <level0> <%= From i In Enumerable.Range(1, 3) _ Select <a:level1 xmlns:a="http://mynamespace1"> <a:level2></a:level2> </a:level1> %> </level0>

When you output doc5, you'll see these results:

<level0 xmlns:a="http://mynamespace1"> <a:level1> <a:level2></a:level2> </a:level1> <a:level1> <a:level2></a:level2> </a:level1> <a:level1> <a:level2></a:level2> </a:level1> </level0>

Here's an example of bubbling at runtime. In this case, the namespace gets bubbled from the <level1> elements at runtime because the compiler does not have enough information to remove them at compile time:

Imports <xmlns:a="http://mynamespace1"> ... Dim doc6 = <a:root> <%= GetElement() %> </a:root> ... Function GetElement() As XElement Return _ <level0> <%= From i In Enumerable.Range(1, 3) _ Select <a:level1> <a:level2></a:level2> </a:level1> %> </level0> End Function

This results in the following output for doc6:

<a:root xmlns:a="http://mynamespace1"> <level0> <a:level1> <a:level2></a:level2> </a:level1> <a:level1> <a:level2></a:level2> </a:level1> <a:level1> <a:level2></a:level2> </a:level1> </level0> </a:root>

It's important to note that namespace bubbling at runtime hurts performance. To achieve optimal performance in this case, it's better to return a single XElement from the function that is called within the embedded expression. When the function returns a single XElement, which itself wraps the query, the impact on performance is negligible because the bubbling and removing of redundant namespaces happens only once—for that element—instead of for each element that the query returns.

Thanks for your registration, follow us on our social networks to keep up-to-date