Tải bản đầy đủ (.pdf) (105 trang)

Mastering Microsoft Visual Basic 2010 phần 7 pps

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (905.51 KB, 105 trang )

598 CHAPTER 14 AN INTRODUCTION TO LINQ

XElement represents an XML element.
◆ XAttribute represents an attribute in an XML element.
These objects can be used to access the document but also to create it. Instead of creating
an XML document directly in your code, you can use the XML helper objects and a structural
approach to create the same document. A simple XML document consists of elements, which
may include attributes. To create a new XElement object, pass the element’s name and value to
its constructor:
New XElement(element_name, element_value)
The following statement will create a very simple XML document:
Dim XmlDoc = New XElement("Books")
MsgBox(XmlDoc.ToString)
You will see the string <Books /> in a message box. This is a trivial, yet valid, XML document.
To create the same book collection as we did earlier by using the helper objects, insert the fol-
lowing statements in a button’s Click event handler:
Dim doc = _
New XElement("Books", _
New XElement("Book", _
New XAttribute("ISBN", "0000000000001"), _
New XElement("Price", 11.95), _
New XElement("Name", "Book Title 1"), _
New XElement("Stock", _
New XAttribute("InStock", 12), _
New XAttribute("OnOrder", 24))), _
New XElement("Book", _
New XAttribute("ISBN", "0000000000002"), _
New XElement("Price", 10.25), _
New XElement("Name", "Book Title 2"), _
New XElement("Stock", _
New XAttribute("InStock", 7), _


New XAttribute("OnOrder", 10))))
I’ve added a twist to the new document to demonstrate the use of multiple attributes in
the same element. The Stock element contains two attributes, InStock and OnOrder.Eachele-
ment’s value can be a basic data type, such as a string or a number, or another element. The
Price element is a decimal value, and the Name element is a string. The Book element, however,
contains three subelements: the Price, Name,andStock elements.
The doc variable is of the XElement type. An XML document is not necessarily based
on the XDocument class. The two basic operations you can perform with an XElement (and
XDocument) object are to save it to a file and reload an XElement object from a file. The
operations are performed with the Save and Load methods, which accept the file’s name as an
argument.
LINQ TO XML 599
Adding Dynamic Content to an XML Document
The XML documents we’ve built in our code so far were static. Because XML support is built
into VB, you can also create dynamic context, and this is where things get quite interesting. To
insert some dynamic content into an XML document, insert the characters <%=. The editor will
automatically insert the closing tag, which is %>. Everything within these two tags is treated
as VB code and compiled. The two special tags create a placeholder in the document (or an
expression hole), and the expression you insert in them is an embedded expression: You embed
a VB expression in your document, and the compiler evaluates the expression and inserts the
result in the XML document.
Here’s a trivial XML document with an embedded expression. It’s the statement that creates
adocumentwithaBook element (I copied it from a code segment presented in the preceding
chapter), and I inserted the current date as an element:
Dim doc = _
New XElement("Books", _
New XElement("Book", _
New XAttribute("ISBN", "0000000000001"), _
New XAttribute("RecordDate", <%= Today %>), _
New XElement("Price", 11.95), _

New XElement("Name", "Book Title 1"), _
New XElement("Stock", _
New XAttribute("InStock", 12), _
New XAttribute("OnOrder", 24))))
Let’s say you have an array of Product objects and you want to create an XML document
with these objects. Listing 14.3 shows the array with the product names.
Listing 14.3: An array of Product objects
Dim Products() As Product = _
{New Product With
{.ProductID = 3, .ProductName = "Product A", _
.ProductPrice = 8.75, _
.ProductExpDate = #2/2/2009#}, _
New Product With _
{.ProductID = 4, .ProductName = "Product B", _
.ProductPrice = 19.5}, _
New Product With _
{.ProductID = 5, .ProductName = "Product C", _
.ProductPrice = 21.25, _
.ProductExpDate = #12/31/2010#}}
The code for generating an XML document with three elements is quite short, but what if
you had thousands of products? Let’s assume that the Products array contains instances of
the Product class. You can use the XMLSerializer class to generate an XML document with the
600 CHAPTER 14 AN INTRODUCTION TO LINQ
array’s contents. An alternative approach is to create an inline XML document with embedded
expressions, as shown in Listing 14.4.
Listing 14.4: An XML document with Product objects
Dim prods = <Products>
<%= From prod In Products _
Select <Product>
<ID><%= prod.ProductID %></ID>

<Name><%= prod.ProductName %></Name>
<Price><%= prod.ProductPrice %></Price>
<ExpirationDate>
<%= prod.ProductExpDate %></ExpirationDate>
</Product> %>
</Products>
This code segment looks pretty ugly, but here’s how it works: In the first line, we start a
new XML document. (The prods variable is actually of the XElement type, but an XElement
is in its own right an XML document.) Notice that there’s no line continuation character at
the end of the first line of the XML document. Then comes a LINQ query embedded in the
XML document with the <%= and %> tags. Notice the line continuation symbol at the end of
this line(_). When we’re in an expression hole, we’re writing VB code, so line breaks matter.
That makes the line continuation symbol necessary. Here’s a much simplified version of the
same code:
Dim prods = <Products>
<%= From prod In Products _
Select <Product>some product</Product> %>
</Products>
This code segment will generate the following XML document:
<Products>
<Product>some product</Product>
<Product>some product</Product>
<Product>some product</Product>
</Products>
The file contains no real data but is a valid XML document. The two tags with the percent
sign switch into VB code, and the compiler executes the statements embedded in them. The
embedded statement of our example is a LINQ query, which iterates through the elements
of the Products array and selects literals (the XML tags shown in the output). To insert data
between the tags, we must switch to VB again and insert the values we want to appear in the
XML document. In other words, we must replace the string some product in the listing with

some embedded expressions that return the values you want to insert in the XML document.
These values are the properties of the Product class, as shown in Listing 14.3. The code shown
in Listing 14.4 will produce the output shown in Listing 14.5.
LINQ TO XML 601
Listing 14.5: An XML document with the data of the array initialized in Listing 14.4
<Products>
<Product>
<ID>3</ID>
<Name>Product A</Name>
<Price>8.75</Price>
<ExpirationDate>2009-02-02T00:00:00</ExpirationDate>
</Product>
<Product>
<ID>4</ID>
<Name>Product B</Name>
<Price>19.5</Price>
<ExpirationDate>0001-01-01T00:00:00</ExpirationDate>
</Product>
<Product>
<ID>5</ID>
<Name>Product C</Name>
<Price>21.25</Price>
<ExpirationDate>2010-12-31T00:00:00</ExpirationDate>
</Product>
</Products>
Transforming XML Documents
A common operation is the transformation of an XML document. If you have worked with
XML in the past, you already know Extensible Stylesheet Language Transformations (XSLT),
which is a language for transforming XML documents. If you’re new to XML, you’ll probably
find it easier to transform XML documents with the LINQ to XML component. Even if you’re

familiar with XSLT, you should be aware that transforming XML documents with LINQ is
straightforward. The idea is to create an inline XML document that contains HTML tags and
an embedded LINQ query, like the following:
Dim HTML = <htlm><b>Products</b>
<table border="all"><tr>
<td>Product</td><td>Price</td>
<td>Expiration</td></tr>
<%= From item In Products.Descendants("Product") _
Select <tr><td><%= item.<Name> %></td>
<td><%= item.<Price> %></td>
<td><%= Convert.ToDateTime( _
item.<ExpirationDate>.Value). _
ToShortDateString %>
</td></tr> %></table>
</htlm>
HTML.Save("Products.html")
Process.Start("Products.html")
602 CHAPTER 14 AN INTRODUCTION TO LINQ
The HTML variable stores plain HTML code. HTML is a subset of XML, and the editor
will treat it like XML: It will insert the closing tags for you and will not let you nest tags in
the wrong order. The Select keyword in the query is followed by a mix of HTML tags and
embedded holes for inline expressions, which are the fields of the item object. Note the VB code
for formatting the date in the last inline expression. The output of the previous listing is shown
in Figure 14.3.
Figure 14.3
AsimpleXMLseg-
ment (top) viewed a s
an HTML table (bottom).
Transformation courtesy
of LINQ.

<products>
<product ProductID=“1” ProductName=“Chai”
UnitPrice=“18.0000” UnitsInStock=“39” UnitsOnOrder=“0” >
</product>
<product ProductID=“2” ProductName=“Chang”
UnitPrice=“19.0000” UnitsInStock=“19” UnitsOnOrder=“40” >
</product>
<product ProductID=“3” ProductName=“Aniseed Syrup”
UnitPrice=“10.0000” UnitsInStock=“26” UnitsOnOrder=“70” >
</product>
<product ProductID=“4” ProductName=“Chef Anton’s Cajun Seasoning”
UnitPrice=“22.0000” UnitsInStock=“128” UnitsOnOrder=“0” >
</product>
<product ProductID=“5” ProductName=“Chef Anton’s Gumbo Mix”
UnitPrice=“21.3600” UnitsInStock=“46” UnitsOnOrder=“0” >
</product>
Products
Product Price On Order
Chai
Chang
Aniseed Syrup
Chef Anton’s Cajun Seasoning
Chef Anton’s Gumbo Mix
Grandma’s Boysenberry Spread
Uncle Bob’s Organic Dried Pears
Northwoods Cranberry Sauce
Mishi Kobe Niku
Queso Cabrales
Queso Manchego La Pastora
Konbu

0
40
70
0
0
0
0
0
0
30
0
0
39
19
26
128
46
179
27
164
50
154
184
95
18.00
19.00
10.00
22.00
21.36
25.01

30.01
40.00
97.00
21.00
38.00
6.00
In Stock
The last two statements save the HTML file generated by our code and then open it in
Internet Explorer (or whichever application you’ve designated to handle by default the HTML
documents).
LINQ TO XML 603
Using Custom Functions with LINQ to XML
The embedded expressions are not limited to simple, inline expressions. You can call custom
functions to transform your data. In a hotel reservation system I developed recently, I had to
transform an XML file with room details to an HTML page. The transformation involved quite
a few lookup operations, which I implemented with custom functions. Here’s a simplified
version of the LINQ query I used in the project. I’m showing the query that generates a simple
HTML table with the elements of the XML document. The RoomType element is a numeric
value that specifies the type of the room. This value may differ from one supplier to another,
so I had to implement the lookup operation with a custom function.
Dim hotels = <html>
<table><tr><td>Hotel</td<td>Room Type</td><td>Price</td></tr>
<%= From hotel In Hotels _
Select <tr><td><%= hotel.<HotelName>.Value %></td>
<td><%= GetRoomType(hotel.<RoomTypeID>)</td>
<td><%= CalculatePrice(hotel.<Base>)</td>
</tr>
%>
</table>
</html>

The GetRoomType() and CalculatePrice() functions must be implemented in the same
module that contains the LINQ query. In my case, they accept more arguments than shown
here, but you get the idea. To speed up the application, I created HashTables using the IDs
of the various entities in their respective tables in the database. The CalculatePrice()
function, in particular, is quite complicated, because it incorporates the pricing policy. Yet,
all the business logic implemented in a s tandard VB function was easily incorporated into the
LINQ query that generates the HTML page with the available hotels and prices.
Another interesting application of XML transformation is the transformation of XML data
into instances of custom objects. Let’s say you need to work with an XML file that contains
product information, and you want to create a list of Product objects out of it. Let’s also assume
that the XML file has the following structure:
<Products>
<product ProductID="1" ProductName="Chai"
CategoryID="1" UnitPrice="18.0000"
UnitsInStock="39" UnitsOnOrder="0" >
</product>
<product ProductID="2" ProductName="Chang"
CategoryID="1" QuantityPerUnit="24 - 12 oz bottles"
UnitPrice="19.0000"
UnitsInStock="19" UnitsOnOrder="40" >
</product>

</Products>
604 CHAPTER 14 AN INTRODUCTION TO LINQ
First, you must load the XML file into an XElement variable with the following statement
(I’m assuming that the XML file is in the same folder as the project):
Dim XMLproducts = XElement.Load(" / /Products.xml")
Now, you can write a LINQ query that generates anonymous types, like the following:
Dim prods = From prod In XMLproducts.Descendants("product")
Select New With {.Name = prod.Attribute("ProductName").Value,

.Price = prod.Attribute("UnitPrice").Value,
.InStock = prod.Attribute("UnitsInStock").Value,
.OnOrder = prod.Attribute("UnitsOnOrder").Value}}
The prods collection consists of objects with the four scalar properties. To make the example
a touch more interesting, let’s say that you don’t want to create a ‘‘flat’’ object. The InStock
and OnOrder properties will become properties of another object, the Stock property. The new
anonymous type will have the following structure:
Product.Name
Product.Price
Product.Stock.InStock
Product.Stock.OnOrder
To create an anonymous type with the revised structure, you must replace the InStock and
OnOrder properties with a new object, the Stock object, which will expose the InStock
and OnOrder properties. The revised query is shown next:
Dim prods =
From prod In XMLproducts.Descendants("product")
Select New With {.Name = prod.Attribute("ProductName").Value,
.Price = prod.Attribute("UnitPrice").Value,
.Stock = New With {
.InStock = prod.Attribute("UnitsInStock").Value,
.OnOrder = prod.Attribute("UnitsOnOrder").Value}}
A simple LINQ query allows you to move from XML into objects and replace the code that
would normally use the XML axis methods (Elements and Descendents)withpureobjects.Of
course, anonymous types can be used only in the context of the procedure in which they were
created. If you want to pass the prods collection between procedures, you should create a new
Product class and use it to create instances of this object, because the anonymous types can’t be
used outside the routine in which they were created. The definition of the Product class, and
the accompanying Stock class, is quite trivial:
Public Class Product
Public Property Name As String

Public Property Price As Decimal
Public Property Stock As Stock
End Class
LINQ TO XML 605
Public Class Stock
Public InStock As Integer
Public OnOrder As Integer
End Class
With the two class definitions in place, you can revise the LINQ query to populate the prod-
ucts collection with instances of the Product class:
Dim Products =
From prod In XMLproducts.Descendants("product")
Select New Product With {
.Name = prod.Attribute("ProductName").Value,
.Stock = New Stock With {
.InStock = prod.Attribute("UnitsInStock").Value,
.OnOrder = prod.Attribute("UnitsOnOrder").Value}}
It shouldn’t come as a surprise that you can iterate through both collections with the same
statements:
For Each p In prods
Debug.WriteLine("PRODUCT: " & p.Name & vbTab &
" PRICE: " & p.Price.ToString &
" STOCK = " & p.Stock.InStock & "/" & p.Stock.OnOrder)
Next
When executed, the preceding statements will generate the following output:
PRODUCT: Grandma’s Boysenberry Spread PRICE: 25.0100 STOCK = 179/0
PRODUCT: Uncle Bob’s Organic Dried Pears PRICE: 30.0100 STOCK = 27/0
PRODUCT: Northwoods Cranberry Sauce PRICE: 40.0000 STOCK = 164/0
Working with XML Files
In this section, we’re going to build a functional interface for viewing customers and orders.

And this time we aren’t going to work with a small sample file. We’ll actually get our data
from one of the sample databases that comes with SQL Server: the Northwind database.
The structure of this database is discussed in Chapter 15, ‘‘Programming with ADO.NET,’’
in detail, but for now I’ll show you how to extract data in XML format from SQL Server. If
you don’t have SQL Server installed or if you’re unfamiliar with databases, you can use the
sample XML files in the folder of the VBLINQ project. Figure 14.4 shows the main form of
the application, which retrieves the same data either from an XML file or directly from the
database.
You may be wondering why you would extract relational data and process them with LINQ
instead of executing SQL statements against the database. XML is the standard data-exchange
format, and you may get data from any other source in this format. You may get an XML file
generated from someone’s database or even an Excel spreadsheet. In the past, you had to con-
vert the data to another, more flexible format and then process it. With LINQ, you can directly
query the XML document, transform it into other formats, and of course save it.
606 CHAPTER 14 AN INTRODUCTION TO LINQ
Figure 14.4
Displaying related data
from XML files
Start SQL Server, and execute the following query:
SELECT * FROM Customers FOR XML AUTO
This statement selects all columns and all rows for the Customers table and generates an
element for each row. The field values are stored in the document as attributes of the corre-
sponding row. The output of this statement is not a valid XML document because its elements
are not embedded in a root element. To request an XML document in which all elements are
embedded in a root element, use the ROOT keyword:
SELECT * FROM Customers FOR XML AUTO, ROOT(’AllCustomers’)
I’m using the root element AllCustomers because the elements of the XML document are
named after the table. The preceding statement will generate an XML document with the
following structure:
<AllCustomers>

<Customers CustomerID="…" CompanyName="xxx" … />
<Customers CustomerID="…" CompanyName="xxx" … />

</AllCustomers>
It would make more sense to generate an XML document with the Customers root element
and name the individual elements Customer. To generate this structure, use the following
statement:
SELECT * FROM Customers Customer FOR XML AUTO, ROOT(’Customers’)
LINQ TO XML 607
Here’s a segment of the XML document with the customers:
<Customers>
<Customer CustomerID="ALFKI" CompanyName=
"Alfreds Futterkiste" ContactName="Maria Anders"
ContactTitle="Sales Representative"
Country="Germany" />
<Customer CustomerID="ANATR" CompanyName=
"Ana Trujillo Emparedados y helados"
ContactName="Ana Trujillo" ContactTitle="Owner"
Country="Mexico" />
Finally, you can create an XML document where the fields are inserted as elements, rather
than attributes. To do so, use the ELEMENTS keyword:
SELECT * FROM Customers Customer FOR XML AUTO,
ELEMENTS ROOT(’Customers’)
The other statements that generated the XML files with the rows of the tables Orders, Order
Details, and Products are as follows:
SELECT * FROM Orders Order FOR XML AUTO, ROOT(’Orders’)
SELECT * FROM [Order Details] Detail FOR XML AUTO,
ELEMENTS, ROOT(’Details’)
SELECT ProductID, ProductName FROM Products
FOR XML AUTO, ELEMENTS ROOT(’Products’)

Notice that all files are attribute based, except for the Details.xml file, which is element
based. I had no specific reason for choosing this structure; I just wanted to demonstrate both
styles for processing XML in the sample project’s code. Also, the reason I’ve included the Prod-
ucts table is because the Order Details table, which contains the lines of the order, stores the
IDs of the products, not the product names. When displaying orders, as shown in Figure 14.4,
you must show product names, not just product IDs. The four collections with the entities we
extracted from the Northwind database are declared and populated at the form’s level via the
following statements:
Dim customers As XElement = XElement.Load(" \ \ \Customers.xml")
Dim orders As XElement = XElement.Load(" \ \ \Orders.xml")
Dim details As XElement = XElement.Load(" \ \ \Details.xml")
Dim products As XElement = XElement.Load(" \ \ \Products.xml")
As is apparent from the code, I’ve placed the four XML files created with the SQL state-
ments shown earlier in the project’s folder. The Display Data button populates the top ListView
control with the rows of the Customers table, via the following statements:
Private Sub bttnShow_Click(…) Handles bttnShow.Click
For Each c In customers.Descendants("Customer")
Dim LI As New ListViewItem
608 CHAPTER 14 AN INTRODUCTION TO LINQ
LI.Text = c.@CustomerID
LI.SubItems.Add(c.@CompanyName)
LI.SubItems.Add(c.@ContactName)
LI.SubItems.Add(c.@ContactTitle)
ListView1.Items.Add(LI)
Next
End Sub
The code is quite simple. It doesn’t even use LINQ; it iterates through the Customer ele-
ments of the customers collection and displays their attributes on the control. Notice the use
of the shortcut for the Attribute property of the current XElement.
When the user clicks a customer name, the control’s SelectedIndexChanged event is fired.

The code in this handler executes a LINQ statement that selects the rows of the Orders table
that correspond to the ID of the selected customer. Then, it iterates through the selected rows,
which are the orders of the current customer, and displays their fields on the second ListView
control via the following statements:
Private Sub ListView1_SelectedIndexChanged(…) _
Handles ListView1.SelectedIndexChanged
If ListView1.SelectedItems.Count = 0 Then Exit Sub
ListView2.Items.Clear()
Dim scustomerID = ListView1.SelectedItems(0).Text
Dim query = From o In orders.Descendants("Order")
Where Convert.ToString(o.@CustomerID) = scustomerID
Select o
For Each o In query
Dim LI As New ListViewItem
LI.Text =
LI.SubItems.Add(Convert.ToDateTime
(o.@OrderDate).ToShortDateString)
LI.SubItems.Add(Convert.ToDecimal(o.@Freight).ToString("#,###.00"))
LI.SubItems.Add()
ListView2.Items.Add(LI)
Next
End Sub
The LINQ query selects Order elements based on their CustomerID attribute. Finally, when
an order is clicked, the following LINQ query retrieves the selected order’s details:
Dim query = From itm In details.Descendants("Detail")
Where Convert.ToInt32(itm.<OrderID>.Value) = orderID
Select itm
The Details.xml file contains elements for all columns, not attributes, and I use statements
such as <dtl.UnitPrice> to access the subelements of the current element. To display
LINQ TO SQL 609

product names, the code selects the row of the Products collection that corresponds to the ID
of each detail line as follows:
Dim product = _
From p In products.Descendants("Product")
Where Convert.ToInt32(p.@ProductID) =
Convert.ToInt32(dtl.<ProductID>.Value)
Select p
The product variable is actually a collection of XElements, even though it can never con-
tain more than a single element (product IDs are unique). You access the ProductName column
of the selected row with the expression product(0)
method to make sure you’ve selected a single product, no matter what:
Dim product = _
(From p In products.Descendants("Product")
Where Convert.ToInt32(p.@ProductID) =
Convert.ToInt32(dtl.<ProductID>.Value)
Select p).First
LINQ to SQL
SQL stands for Structured Query Language, a language for querying databases. SQL is dis-
cussed in the last part of this book, and as you will see, SQL resembles LINQ. SQL is a simple
language, and I will explain the SQL statements used in the examples; readers who are some-
what familiar with databases should be able to follow along.
Now, let’s build another application for displaying customers, orders, and order details. The
difference is that this time you won’t get your data from an XML document; you’ll retrieve
them directly from the database. As you will see, the same LINQ queries will be used to pro-
cess the rows returned by the queries. The code won’t be identical to the code presented in the
preceding section, but the differences are minor. The same principles will be applied to a very
different data source.
You need a mechanism to connect to the database so you can retrieve data, and this mech-
anism is the DataContext class. The DataContext class talks to the database, retrieves data, and
submits changes back to the database. To create a DataContext object, pass a string with the

information about the database server, the specific database, and your credentials to the Data-
Context class’s constructor, as shown here:
Dim db As New DataContext("Data Source=localhost;
initial catalog=northwind;
Integrated Security=True")
To use the DataContext class in your code, you must add a reference to the System.Data
.Linq namespace and then import it into your code with this statement:
Imports System.Data.Linq
610 CHAPTER 14 AN INTRODUCTION TO LINQ
You will find more information on connecting to databases in Chapter 15. For the purposes
of this chapter, the preceding connection string will connect your application to the Northwind
database on the local database server, provided that you have SQL Server or SQL Server
Express installed on the same machine as Visual Studio. If you do not, replace ‘‘localhost’’
in the connection string with the name or IP address of the machine on which SQL Server is
running.
After you have initialized the DataContext object, you’re ready to read data from tables into
variables. To do so, call the GetTable method of the db object to retrieve the rows of a table.
Note that the name of the table is not specified as an argument. Instead, the table is inferred
from the type passed to the GetTable method as an argument. The GetTable(Of Customer)
method will retrieve the rows of the Customers table, because the name of the table is specified
in the definition of the class, as you will see shortly.
customers = From cust In db.GetTable(Of Customer)()
Select New Customer With
{.CustomerID = cust.CustomerID,
.CompanyName = cust.CompanyName,
.ContactName = cust.ContactName,
.ContactTitle = cust.ContactTitle}
orders = From ord In db.GetTable(Of Order)()
Select New Order With
{.OrderID = ord.OrderID,

.OrderDate = ord.OrderDate,
.CustomerID = ord.CustomerID,
.Freight = ord.Freight,
.ShipName = ord.ShipName}
details = From det In db.GetTable(Of Detail)()
Select New Detail With
{.OrderID = det.OrderID,
.ProductID = det.ProductID,
.Quantity = det.Quantity,
.UnitPrice = det.UnitPrice,
.Discount = det.Discount}
products = From prod In db.GetTable(Of NWProduct)()
Select New NWProduct With
{.ProductID = prod.ProductID,
.ProductName = prod.ProductName}
The type of the customers, orders, details, and products variables is IQueryable(of entity),
where entity is the appropriate type for the information you’re reading from the database. The
four variables that will store the rows of the corresponding tables must be declared at the form
level with the following statements:
Dim customers As System.Linq.IQueryable(Of Customer)
Dim orders As System.Linq.IQueryable(Of Order)
Dim details As System.Linq.IQueryable(Of Detail)
Dim products As System.Linq.IQueryable(Of NWProduct)
The variables must be declared explicitly at the form level, because they will be accessed from
within multiple event handlers.
LINQ TO SQL 611
To make the most of LINQ to SQL, you must first design a separate class for each table that
you want to load from the database. You can also specify the mapping between your classes
and the tables from which their instances will be loaded, by prefixing them with the appropri-
ate attributes. The Customer class, for example, will be loaded with data from the Customers

table. To specify the relationship between the class and the table, use the Table attribute, as
shown here:
<Table(Name:="Customers")>Public Class Customer
End Class
Each property of the Customer class will be mapped to a column of the Customers table. In
a similar manner, decorate each property with the name of the column that will populate the
property:
<Column(Name:="CompanyName")>Public Property Name
End Property
If the name of the property matches the name of the relevant column, you can omit the col-
umn’s name:
<Column()>Public Property Name
End Property
Listing 14.6 shows the definition of the four classes we’ll use to store the four tables (Cus-
tomers, Orders, Order Details, and Products).
Listing 14.6: The classes for storing customers and orders
<Table(Name:="Customers")> Public Class Customer
Private _CustomerID As String
Private _CompanyName As String
Private _ContactName As String
Private _ContactTitle As String
<Column()> Public Property CustomerID() As String
Get
Return _customerID
End Get
Set(ByVal value As String)
_customerID = value
End Set
End Property
<Column()> Public Property CompanyName() As String

Get
Return _CompanyName
End Get
Set(ByVal value As String)
_CompanyName = value
612 CHAPTER 14 AN INTRODUCTION TO LINQ
End Set
End Property
<Column()> Public Property ContactName() As String
….
End Property
<Column()> Public Property ContactTitle() As String
….
End Property
End Class
<Table(Name:="Orders")> Public Class Order
Private _OrderID As Integer
Private _CustomerID As String
Private _OrderDate As Date
Private _Freight As Decimal
Private _ShipName As String
<Column()> Public Property OrderID() As Integer
….
End Property
<Column()> Public Property CustomerID() As String
….
End Property
<Column()> Public Property OrderDate() As Date
….
End Property

<Column()> Public Property Freight() As Decimal
….
End Property
<Column()> Public Property ShipName() As String
….
End Property
End Class
<Table(Name:="Order Details")> Public Class Detail
Private _OrderID As Integer
Private _ProductID As Integer
Private _Quantity As Integer
Private _UnitPrice As Decimal
Private _Discount As Decimal
<Column()> Public Property OrderID() As Integer
LINQ TO SQL 613
….
End Property
<Column()> Public Property ProductID() As Integer
….
End Property
<Column()> Public Property Quantity() As Short
….
End Property
<Column()> Public Property UnitPrice() As Decimal
….
End Property
<Column()> Public Property Discount() As Double
….
End Property
End Class

<Table(Name:="Products")> Public Class NWProduct
Private _ProductID As Integer
Private _ProductName As String
<Column()> Public Property ProductID() As Integer
….
End Property
<Column()> Public Property ProductName() As String
….
End Property
End Class
I didn’t show the implementation of most properties, because it’s trivial. What’s interest-
ing in this listing are the Table and Column attributes that determine how the instances of the
classes will be populated from the database, as you saw earlier.
The code that displays the selected customer’s orders and the selected order’s details is sim-
ilar to the code you saw in the previous section that displays the data from the XML files. It
selects the matching rows in the relevant table and shows them in the corresponding ListView
control.
Retrieving Data with the ExecuteQuery Method
You can also retrieve a subset of the table, or combine multiple tables, by executing a SQL
query against the database. The ExecuteQuery method, which accepts as arguments the SELECT
statement to be executed and an array with parameter values, returns a collection with the
614 CHAPTER 14 AN INTRODUCTION TO LINQ
selected rows as objects. To call the ExecuteQuery method, you must specify the class that
will be used to store the results with the Of keyword in parentheses following the method’s
name. Then you specify the SELECT statement that will retrieve the desired rows. If this query
contains any parameters, you must also supply an array of objects with the parameter values.
Parameters are identified by their order in the query, not by a name. The first parameters is 0,
the second parameter is 1, and so on. The following statement will retrieve all customers from
Germany and store them in instances of the Customer class:
Dim params() = {"Germany"}

Dim GermanCustomers = _
db.ExecuteQuery(Of Customer)( _
"SELECT CustomerID, CompanyName," & _
"ContactName, ContactTitle " &
"FROM Customers WHERE Country={0}", params)"
After the GermanCustomers collection has been populated, you can iterate through its items
as usual, with a loop like the following:
For Each cust In GermanCustomers
Debug.WriteLine(cust.CompanyName & " " & _
cust.ContactName)
Next
Once you have retrieved the results from the database, you can execute LINQ queries
against the collection. To find out the number of customers from Germany, use the following
expression:
Dim custCount = GermanCustomers.Count
To apply a filtering expression and then retrieve the count, use the following LINQ expression:
Dim g = GermanCustomers.Where(Function(c As Customer) _
c.CompanyName.ToUpper Like "*DELIKATESSEN*").Count
To appreciate the role of the DataContext class in LINQ to SQL, you should examine the
ToString property of a LINQ query that’s executed against the database. Insert a statement to
display the expression GermanCustomers.ToString() in your code, and you will see that the
DataContext class has generated and executed the following statement against the database. If
you’re familiar with SQL Server, you can run the SQL Server Profiler and trace all commands
executed against SQL Server. Start SQL Server Profiler (or ask the database administrator to cre-
ate a log of all statements executed by your workstation against a specific database), and then
execute a few LINQ to SQL queries. Here’s the statement for selecting the German customers
as reported by the profiler:
exec sp_executesql N’SELECT Customers.CompanyName,
Orders.OrderID, SUM(UnitPrice*Quantity) AS
OrderTotal FROM Customers INNER JOIN Orders

LINQ TO SQL 615
ON Customers.CustomerID = Orders.CustomerID
INNER JOIN [Order Details] ON
[Order Details].OrderID = Orders.OrderID
WHERE Customers.Country=@p0
GROUP BY Customers.CompanyName,
Orders.OrderID’,N’@p0 nvarchar(7)’,@p0=N’Germany’
Working with LINQ to SQL Classes
The process of getting data out of a database and into a custom class is as straightforward as it
can get. You create a class with properties that match the columns of the equivalent table, and
then you use the DataContext object to populate these classes. You may be thinking already
about a class generator that will take care of the mapping between the class properties and
the table columns. Visual Studio does that for you with a component called LINQ to SQL
Classes.
A LINQ to SQL Classes component encapsulates a segment of a database, or the entire
database, and lets you work against a database as if the database entities were objects. While
in traditional database programming you code against tables that are made up of rows, with
LINQ to SQL Classes you will work against the same database, only this time the tables will
be collections made up of custom objects. The Customers table of the Northwind database,
for example, contains rows that represent customers. When you work with a LINQ to SQL
Classes component, the Customers table becomes a collection, and its rows become instances of
Customer objects. As you will see shortly, the idea behind LINQ to SQL Classes is to bridge
the gap between traditional database programming and the object-oriented features of modern
programming languages. You’ll see the advantages of accessing databases as collections of
strongly typed objects in just a few pages.
To add this component to your solution, right-click the solution name, and from the context
menu select Add New Item. In the Add New Item dialog box, select the LINQ to SQL Classes
component, as shown in Figure 14.5, and set the component’s name (use the NWind name for
this example).
Figure 14.5

Start by adding a LINQ
to SQL Classes compo-
nent to your project.
616 CHAPTER 14 AN INTRODUCTION TO LINQ
Once the new component has been added to the project, the Server Explorer window
will open. Here you can select a connection to one of the databases on your system (I’m
assuming you have installed either SQL Server or SQL Server Express). Create a new
connection to the Northwind database if you don’t already have a connection to this
database, and open it. If you don’t know how to create connections to databases, follow this
procedure:
1. Switch to Server Explorer, and right-click the Data Connections item. From the context
menu, select Add Connection to open the dialog box shown in Figure 14.6.
2. In the Add Connection dialog box that appears, select the name of the database server you
want to use. I’m assuming that most readers have a version of SQL Server 2008 installed
on their machines, so you can specify localhost as the server name. If you’re connected to
a remote database server on the network, the database administrator will give the proper
database name and credentials.
Figure 14.6
Creating a new database
connection
3. Select the authentication method for the database server. Again, most readers can select the
option Use Windows Authentication. To connect to a remote server, you will most likely
LINQ TO SQL 617
have to select Use SQL Server Authentication and supply your credentials, as shown in
Figure 14.6.
4. Expand the list of databases in the drop-down list in the lower pane of the dialog box,
and select Northwind. If you haven’t installed the Northwind database, then you should
download and install it, as explained in Chapter 15.
As soon as you close the Add Connection dialog box, the designer will add a new
component to the class, the DataClasses1.dbml component, and will open it in design

mode. DataClasses1 is the default name of a LINQ to SQL Classes component, and I suggest
you change the name to something more meaningful. The VBLINQ project uses the name
TableClasses.
The designer is initially an empty space. But here’s how easy it is to create custom objects
based on the database entities, as shown in Figure 14.7.
Figure 14.7
Designing a LINQ to SQL
Classes class with visual
tools
Server Explorer will display all the items in the database. Select the Customers, Orders
and Order Details tables from Server Explorer, and drop them onto the designer’s sur-
face. The designer will pick up the relations between the tables from the database
and will depict them as arrows between related classes. It will also create the appro-
priate classes on the fly, one for each table. Specifically, the designer will create the
Customer, Order, and Order_Detail classes that represent the entities stored in the Customers,
Orders, and Order Details tables. Notice how the designer singularized the names of the
entities.
The designer has also created three collections to represent the three tables, as well as a
DataContext object to connect to the database. To exercise the autogenerated classes, build the
sample form shown in Figure 14.8. This form loads the countries and uses their names to pop-
ulate the ComboBox control at the top. Every time the user selects a country, the application
makes another trip to the database, retrieves the customers in the selected country, and displays
the customer names on the Select Customer ListBox control.
618 CHAPTER 14 AN INTRODUCTION TO LINQ
Figure 14.8
A form for viewing cus-
tomers, their orders,
and the details for each
order
Once a customer has been selected, the application makes another trip to the database

and selects the customer’s orders, which are displayed on the top ListView control.
Finally, when an order is selected, the application reads the order’s details and dis-
plays them on the lower ListView control on the right. The idea is to get as little
information as possible from the database depending on the user’s action. There’s no
need to retrieve all the customers when the application starts, because the user many
not even view any customer. In general, you should try to limit the user’s selection so
that you can minimize the information you request from the database and download
at the client. Although there will be times you need to minimize trips to the database,
in that case you will pull data that you might need and then possibly throw some of it
away.
The DataContext Object
The following statement creates a new DataContext object for accessing the Northwind
database:
Dim ctx As New NwindDataContext
The NWindDataContext class was generated by the designer; it gives you access to the
database’s tables (and stored procedures). The database tables are properties of the ctx
variable, and they return an IQueryable collection with the rows of each table. To access the
Customers table, for example, request the Customers property:
Ctx.Customers
Each item in the Customers collection is an object of the Customer type. The designer also gen-
erated a class for the entities stored in each of the tables. Not only that, but it singularized the
names of the tables.
LINQ TO SQL 619
Accessing the Tables with LINQ
Since the Customers property of the ctx variable returns the rows of the Customers table as
a collection, you can use LINQ to query the table. The following query returns the German
customers:
Dim germanCustomers = From cust In ctx.Customers
Where cust.Country = "Germany"
Select cust

The compiler knows that cust is a variable of the Customer type, so it displays the fields
of the Customers table (which are now properties of the Customer object) in the IntelliSense
drop-down list. In effect, the LINQ to SQL component has mapped the selected tables into
objects that you can use to access your database using OOP techniques.
But the LINQ to SQL Classes component has done much more. germanCustomers is a query
that isn’t executed until you request its elements. The expression ctx.Customers doesn’t move
the rows of the Customers table to the client so you can query them. Instead, it parses your
LINQ query, builds the appropriate SQL query, and executes it when you iterate through the
query results. To see the queries that are executed as per your LINQ query when they’re sub-
mitted to the database, insert the following simple statement right after the declaration of the
ctx variable:
ctx.Log = Console.Out
This statement tells the compiler to send all the commands that the DataContext object submits
to the database to the Output window. Place a button on the main form of the project, and in
its Click event handler insert the following statements:
ctx.Log = Console.Out
Dim selCustomers = From cust In ctx.Customers
Where cust.Country = "Germany"
Select cust
MsgBox("No query executed so far!")
For Each cust In selCustomers
ListBox1.Items.Add(cust.CustomerID & vbTab & cust.CompanyName)
Next
Execute these statements, and watch the Output window. The message box will be displayed
and nothing will be shown in the Output window, because no data has been requested from
the database yet. selCustomers is just a query that the compiler has analyzed, but it hasn’t
been executed yet. As soon as you close the message box, the following SQL statement will be
submitted to the database to request some data:
SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName],
[t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region],

[t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [dbo].[Customers] AS [t0]
620 CHAPTER 14 AN INTRODUCTION TO LINQ
WHERE [t0].[Country] = @p0
@p0: Input NVarChar (Size = 4000; Prec = 0; Scale = 0) [Germany]
Context: SqlProvider(Sql2008) Model: AttributedMetaModel Build: 4.0.20506.1
If you’re not familiar with SQL, don’t panic. You’ll find the basics later in this book. If you’re
a totally result-oriented developer, don’t even bother with the SQL statement; VB does it all for
you. It knows how to request the data you need, and it won’t bring even one extra row from
the Customers table back to the client. Of course, you shouldn’t iterate through all the rows of
the table, because this could ruin application performance. Never, never bring an entire table
to the client, unless it’s a small table (categories, state names, and so on). Even then, it’s not a
good idea to keep the data at the client for long periods of time; other users may edit the data
at the database, and when that happens, the data at the client become ‘‘stale,’’ because the
data at the client are not directly associated with the database and will not be updated. The
short of the story is that the DataContext object establishes a connection to the database and
lets you view your database tables as collections of objects. Use it to grab the data you need,
and submit changes as the user edits the data.
To limit the number of rows you bring to the client, try to give users the option to specify
selection criteria. The sample application you’ll build in this section requests that users select
a country and then brings only the customers from that particular country to the client. Actu-
ally, you don’t even need to bring all the fields of the Customers table. All you will need is
the CompanyName that’s displayed on the ListBox control and the ID that you’ll use in subse-
quent queries to identify the selected customer. If you have too much data, you can limit the
number of rows you bring to the client (to an arbitrary value, say, 1,000 rows). If the user has
selected more rows, they should specify a more specific search pattern to limit the number of
rows. After all, who needs to see thousands and thousands of rows just because they exist?
Navigation Methods
Tables are rarely isolated in a database, and no one ever really cares about the rows of a sin-
gle table. Tables are (almost always) related to one another. Customers place orders, and orders

contain products and prices. Orders belong to customers, but they’re created by employees.
The most common operation in data-driven applications is not entering new rows or editing
existing ones. Instead, data-driven applications spend most of their time going from a row in
a specific table to one or more related rows in another table. Of course, retrieving the related
rows implies that you will also design an interface to display the data you gather from mul-
tiple tables to the user. Let’s say you have landed on a specific customer, represented by the
selCustomer variable (it’s a variable of the Customer type, of course). You could select a cus-
tomer by index, with a LINQ query, or let the user select it from a list. The idea is that you
have retrieved the customer you’re interested in.
To access the related rows in other tables, you can request the property of the Customer
object that corresponds to the related table. To access the orders of the customer represented
by the cust object, use the following expression:
cust.Orders
This expression returns an IQueryable collection as usual, with the rows of the Orders table
that correspond to the selected customer. The Orders property represents the Orders table and
returns an IQueryable collection of Order objects, each representing an order, as you might
expect. However, it doesn’t return all rows of the Orders table — just the ones that belong to
the selected customer. Each Order object in turn exposes the columns of the Orders table as
LINQ TO SQL 621
properties. To access the OrderDate field of the first order of the first customer in the german-
Customers collection, you’d use an expression like the following:
selCustomers.ToList(0).Orders.ToList(0).OrderDate
YouhavetoapplytheToList operator to every collection to force the execution of the
appropriate query and then select a specific item of the collection.
Now we can take a closer look at the code of the sample project code. The following code
loads all countries and displays their names on the ListBox control:
Private Sub Button1_Click( ) Handles Button1.Click
ctx = New NWindDataContext
ctx.Log = Console.Out
Dim countries = From cust In ctx.Customers

Select cust.Country Distinct
For Each country As String In countries
cbCountries.Items.Add(country)
Next
End Sub
The very first query is peculiar indeed. The Northwind database doesn’t store the countries
in a separate table, so you have to go through the Customers table and collect all the unique
countrynames.ThisiswhattheDistinct keyword does: It forces the query to return each
unique country name from the Customers table only once. LINQ doesn’t download all the rows
of the Customers table to the client to select the unique country names. The actual query sent
to the database by LINQ is the following, which instructs SQL Server to return only the unique
country names:
SELECT DISTINCT [t0].[Country]
FROM [dbo].[Customers] AS [t0]
LINQ is very efficient when it comes to talking to the database, and you will see shortly how
you can monitor the queries it submits to the database.
When the user selects a customer on the ListBox control, the statements included in
Listing 14.7 are executed to display the number of orders placed by the customer and the total
revenue generated by the selected customer, as well as the headers of all orders.
Listing 14.7: Retrieving the orders of the selected customer
Private Sub ListBox1_SelectedIndexChanged( )
Handles ListBox1.SelectedIndexChanged
If ListBox1.SelectedItem Is Nothing Then Exit Sub
Dim custID As String = ListBox1.SelectedItem.ToString.Substring(0, 5)
Dim custName As String = ListBox1.SelectedItem.ToString.Substring(5).Trim
Dim customerOrders = From ord In ctx.Orders
Where ord.CustomerID = custID
Select New With {.order = ord, .details = ord.Order_Details}
Dim orders = customerOrders.Count
If orders > 0 Then

622 CHAPTER 14 AN INTRODUCTION TO LINQ
Dim tot = From o In customerOrders
Select Aggregate det In o.details Into
Sum(det.UnitPrice * det.Quantity * (1 - det.Discount))
TextBox1.Text = "Customer " & custName & " has placed " &
orders.ToString & " orders totalling $" &
tot.Sum.ToString
Else
TextBox1.Text = "There are no order for customer " & custName
End If
lvOrders.Items.Clear()
For Each ord In customerOrders
Dim LI As New ListViewItem
LI.Text = ord.order.OrderID.ToString
LI.SubItems.Add(ord.order.OrderDate.Value.ToShortDateString)
LI.SubItems.Add((Aggregate dtl In ord.details Into
Sum(dtl.UnitPrice * dtl.Quantity *
(1 - dtl.Discount))).ToString)
LI.SubItems.Add(Aggregate dtl In ord.details Into Sum(dtl.Quantity))
LI.SubItems.Add(ord.order.Freight.ToString)
lvOrders.Items.Add(LI)
Next
End Sub
The code extracts the selected customer’s ID from the ListBox control and stores it to the
custID variable. It uses this variable to select the customer’s orders into the customerOrders
collection. Next, it calculates the number of orders and the total revenue generated by the cus-
tomer and displays it on the form. Finally, it iterates through the customerOrders collection
and displays the orders on a ListView control. One of the items shown in the ListView con-
trol is the total of each order, which is calculated by another LINQ query that aggregates the
current order’s details:

Aggregate dtl In ord.details Into
Sum(dtl.UnitPrice * dtl.Quantity * (1 - dtl.Discount))
This query returns a Double value, which is formatted and displayed like a variable. The query
isn’t assigned to a variable, and there’s no Select clause — just the aggregate value.
You may be tempted to write a loop that iterates through all the rows in the Customers table
to calculate aggregates. Just don’t! Use LINQ to formulate the appropriate query, and then let
the compiler figure out the statement that must be executed against the database to retrieve the
information you need, and no more. If you execute a loop at the client, LINQ to SQL will move
all the rows of the relevant tables to the client, where the loop will be executed. Although this
may work for Northwind, as the database grows larger it will be an enormous burden on the
database and the local network. The query might get a little complicated, but it saves you from
the performance issues you’d face when the application is released to many clients. Eventually,
you will be forced to go back and rewrite your code.
Let’s say you need to know the revenue generated by all customers in each country and in
a specific year. The following LINQ query does exactly that and returns a collection of anony-
mous types with just two fields: the country name and the total revenue per country.

×