can potentially lead to some issues in debugging and some unexpected performance issues if you are
executing the query multiple times. In the code you have seen so far, you have declared the LINQ
statement and then passed the results object to the ObjectDumper, which in turn iterates through
the query results. If you were to repeat this call to the ObjectDumper, it would again iterate
through the results.
Unfortunately, this delayed execution can mean that LINQ statements are hard to debug. If you
select the statement and insert a breakpoint, all that will happen is that the application will stop
where you have declared the LINQ statement. If you step to the next line, the results object will
simply state that it is an “In-Memory Query.” In C# the debugging story is slightly better because
you can actually set breakpoints within the LINQ statement. As you can see from Figure 28-8,
the breakpoint on the conditional statement has been hit. From the call stack you can see that
the current execution point is no longer actually in the FinalQuery method; it is in fact within the
ObjectDumper.Write
method.
fiGure 28-8
If you need to force the execution of a LINQ you can call ToArray or ToList on the results
object. This will force the query to execute, returning an Array or List of the appropriate type.
You can then use this array in other queries, reducing the need for the LINQ to be executed
multiple times.
linQ to XMl .
597
When setting a breakpoint within a LINQ in C# you need to place
the cursor at the point you want the breakpoint to be set and press F9 (or
use the right-click context menu to set a breakpoint), rather than clicking in
the margin. Clicking in the margin sets a breakpoint on the whole LINQ,
which is not what you want.
linq to xMl
If you have ever worked with XML in .NET, you will recall that the object model isn’t as easy
to work with as you would imagine. For example, to create even a single XML element you need to
have an XmlDocument:
Dim x as New XmlDocument
x.AppendChild(x.CreateElement("Customer"))
As you will see when you start to use LINQ to query and build XML, this object model doesn’t
allow for the inline creation of elements. To this end, a new XML object model was created that
resides in the System.Xml.Linq assembly presented in Figure 28-9.
fiGure 28-9
As you can see from Figure 28-9, there are classes that correspond to the relevant parts of an XML
document: XComment, XAttribute, and XElements. The biggest improvement is that most of the
classes can be instantiated by means of a constructor that accepts Name and Content parameters.
In the following C# code, you can see that an element called Customers has been created that
contains a single Customer element. This element, in turn, accepts an attribute, Name, and a series
of Order elements.
c#
XElement x = new XElement("Customers",
new XElement("Customer",
new XAttribute("Name","Bob Jones"),
598 .
chaPter 28 lAnguAge inTegrATed QuerieS (linQ)
new XElement("Order",
new XAttribute("Product", "Milk"),
new XAttribute("Quantity", 2)),
new XElement("Order",
new XAttribute("Product", "Bread"),
new XAttribute("Quantity", 10)),
new XElement("Order",
new XAttribute("Product", "Apples"),
new XAttribute("Quantity", 5))
)
);
Though this code snippet is quite verbose and it’s hard to distinguish the actual XML data from
the surrounding .NET code, it is significantly better than with the old XML object model, which
required elements to be individually created and then added to the parent node.
While it is possible to write the same code in VB using the XElement and
XAttribute constructors, the support for XML literals (as discussed in the next
section) makes this somewhat redundant.
Vb xMl literals
One of the biggest innovations in the VB language is the support for XML literals. As with strings
and integers, an XML literal is treated as a first-class citizen when you are writing code. The
following snippet illustrates the same XML generated by the previous C# snippet as it would appear
using an XML literal in VB:
Vb
Dim cust = < Customers >
< Customer Name="Bob Jones" >
< Order Product="Milk" Quantity="2"/ >
< Order Product="Bread" Quantity="10"/ >
< Order Product="Apples" Quantity="5"/ >
< /Customer >
< /Customers >
Not only do you have the ability to assign an XML literal
in code, you also get designer support for creating and
working with your XML. For example, when you enter
the
closing XML tag for you. Figure 28-10 illustrates how
the Customers XML literal can be condensed in the same
fiGure 28-10
way as other code blocks in Visual Studio 2010.
You can also see in Figure 28-10 that there is an error in the XML literal being assigned to the
data variable. In this case there is no closing tag for the Customer element. Designer support is
invaluable for validating your XML literals, preventing run time errors when the XML is parsed
into XElement objects.
on a new element, it will automatically create the >
linQ to XMl .
599
Paste xMl as xelement
Unfortunately, C# doesn’t have native support for XML literals, which makes generating XML
a painful process, even with the new object model. Luckily, there is a time-saving add-in that
will paste an XML snippet from the clipboard into the code window as a series of XElement
objects. This can make a big difference if you have to create XML from scratch. The add-in,
PasteXmlAsLinq in the LinqSamples folder, is available in the C# samples that ship with
Visual Studio 2010. Simply open the sample in Visual Studio 2010, build the solution, navigate
to the output folder, and copy the output files (namely
PasteXmlAsLinq.Addin and PasteXmlAsLinq.dll) to the
add-ins folder for Visual Studio 2010. When you restart Visual
Studio 2010 you will see a new item, Paste XML as XElement, in
the Edit menu when you are working in the code editor window,
as you can see in Figure 28-11. fiGure 28-11
Visual Studio 2010 looks in a variety of places, defined in the Options dialog
(Tools menu), for add-ins. Typically, it looks in an add-ins folder located beneath
the Visual Studio root documents directory. For example: C:\users\username\
Documents\Visual Studio 2010\Addins. If the Addins folder doesn’t exist,
you may need to create it.
To work with this add-in, all you need to do is to
create the XML snippet in your favorite XML editor.
In Figure 28-12 we have used XML Notepad, which
is a freely available download from www.microsoft.
com, but you can also use the built-in XML editor
within Visual Studio 2010.
Once you have created the XML snippet, copy it to
the clipboard (for example, by pressing Ctrl1C). Then
place your cursor at the point at which you want
to insert the snippet within Visual Studio 2010 and
select Paste XML as XElement from the Edit menu.
(Of course, if you use this option frequently you may
want to assign a shortcut key to it so that you don’t
have to navigate to the menu.) The code generated by
the add-in will look similar to the following:
c#
XElement xml = new XElement("Customers",
new XElement("Customer",
new XAttribute("Name", "Bob Jones"),
new XElement("Order",
new XAttribute("Product", "Milk"),
new XAttribute("Quantity", "2")
fiGure 28-12
600 .
chaPter 28 lAnguAge inTegrATed QuerieS (linQ)
),
new XElement("Order",
new XAttribute("Product", "Bread"),
new XAttribute("Quantity", "10")
),
new XElement("Order",
new XAttribute("Product", "Apples"),
new XAttribute("Quantity", "5")
)));
Code snippet MainForm.cs
creating xMl with linq
Although creating XML using the new object model is significantly quicker than previously
possible, the real power of the new object model comes when you combine it with LINQ in the
form of LINQ to XML (XLINQ). By combining the rich querying capabilities with the ability to
create complex XML in a single statement, you can now generate entire XML documents in a single
statement. Let’s continue with the same example of customers and orders. In this case you have an
array of customers, each of whom has any number of orders. What you want to do is create XML
that lists the customers and their associated orders. You’ll start by creating the customer list, and
then introduce the orders.
To begin with, create an XML literal that defines the structure you want to create:
c#
XElement customerXml = new XElement("Customers",
new XElement("Customer",
new XAttribute("Name", "Bob Jones")));
Vb
Dim customerXml = <Customers>
<Customer Name="Bob Jones">
</Customer>
</Customers>
Although you can simplify this code by condensing the Customer element into <Customer
Name=”Bob Jones” />, you’re going to be adding the orders as child elements, so you will use a
separate closing XML element.
expression Holes
If you have multiple customers, the Customer element is going to repeat for each one, with Bob
Jones being replaced by different customer names. Before you deal with replacing the name, you
first need to get the Customer element to repeat. You do this by creating an expression hole, using a
syntax familiar to anyone who has worked with ASP:
linQ to XMl .
601
c#
XElement customerXml = new XElement("Customers",
from c in customers
select new XElement("Customer",
new XAttribute("Name",
"Bob Jones")));
Vb
Dim customerXml = <Customers>
<%= From c In customers _
Select <Customer Name="Bob Jones">
</Customer> %>
</Customers>
Here you can see that in the VB code, <%= %> has been used to define the expression hole, into
which a LINQ statement has been added. This is not required in the C# syntax because the LINQ
statement just becomes an argument to the XElement constructor. The Select statement creates
a projection to an XML element for each customer in the Customers array, based on the static
value “Bob Jones”. To change this to return each of the customer names you again have to use an
expression hole. Figure 28-13 shows how Visual Studio 2010 provides rich IntelliSense support in
these expression holes.
fiGure 28-13
The following snippet uses the loop variable Name so that you can order the customers based on
their full names. This loop variable is then used to set the Name attribute of the customer node.
c#
XElement customerXml = new XElement("Customers",
from c in customers
let name = c.FirstName + " " + c.LastName
orderby name
select new XElement("Customer",
new XAttribute("Name", name),
from o in c.Orders
select new XElement("Order",
new XAttribute("Product", o.Product),
new XAttribute("Quantity",
o.Quantity))));
Code snippet MainForm.cs
602 .
chaPter 28 lAnguAge inTegrATed QuerieS (linQ)
Vb
Dim customerXml = <Customers>
<%= From c In customers _
Let Name = c.FirstName & “ “ & c.LastName _
Order By Name _
Select <Customer Name=<%= Name %>>
<%= From o In c.Orders _
Select
<Order
Product=<%= o.Product %>
Quantity=<%= o.Quantity %>
/> %>
</Customer> %>
</Customers>
Code snippet MainForm.vb
The other thing to notice in this snippet is that you have included the creation of the Order elements
for each customer. Although it would appear that the second, nested LINQ statement is independent
of the first, there is an implicit joining through the customer loop variable c. Hence, the second
LINQ statement is iterating through the orders for a particular customer, creating an Order element
with attributes Product and Quantity.
As you can see, the C# equivalent is slightly less easy to read but is by no means more complex.
There is no need for expression holes, because C# doesn’t support XML literals; instead, the LINQ
statement just appears nested within the XML construction. For a complex XML document this
would quickly become difficult to work with, which is one reason VB now includes XML literals as
a first-class language feature.
queryinG xMl
In addition to enabling you to easily create XML, LINQ can also be used to query XML. The
following Customers XML is used in this section to discuss the XLINQ querying capabilities:
<Customers>
<Customer Name="Bob Jones">
<Order Product="Milk" Quantity="2"/>
<Order Product="Bread" Quantity="10"/>
<Order Product="Apples" Quantity="5"/>
</Customer>
</Customers>
The following two code snippets show the same query using VB and C#, respectively. In both cases
the customerXml variable (an XElement) is queried for all Customer elements, from which the Name
attribute is extracted. The Name attribute is then split over the space between names, and the result
is used to create a new Customer object.
schema support .
603
c#
var results = from cust in customerXml.Elements("Customer")
let nameBits = cust.Attribute("Name").Value.Split(' ')
select new Customer() {FirstName = nameBits[0],
LastName=nameBits[1] };