Chapter 18
Using LINQ to DataSet
After completing this chapter, you will be able to:
Prepare a DataTable instance so that it uses the IEnumerable interface
Treat ADO.NET table values as first-class members of a LINQ query
Cast type-neutral column values as strongly typed query values
LINQ processes data from a variety of sources, but those sources must first be expressed in a
form that LINQ can use. For instance, LINQ expects that all incoming data be stored in a col-
lection, one that conforms to either the IEnumerable(Of T) or the IQueryable(Of T) interface.
The LINQ to DataSet provider endows ordinary ADO.NET DataTable objects with the ability
to participate fully in LINQ queries. It does this by adding the necessary LINQ requirements
to relevant ADO.NET classes. This chapter introduces these enhancements and shows you
how to employ them to extract data from data sets using the power of LINQ.
Understanding the LINQ to DataSet Provider
ADO.NET’s DataTable class, as a logical collection of data-laden objects, is the perfect can-
didate for inclusion in LINQ queries. Unfortunately, it exhibits two aspects that make it less
than useful with LINQ: (1) it implements neither IEnumerable(Of T) nor IQueryable(Of T), and
(2) the data values contained in each DataRow instance exist as System.Object instances
and only indirectly express their true types through DataColumn definitions.
To overcome these deficiencies, the LINQ to DataSet provider adds new extension methods
to both the DataTable and DataRow classes. These new features appear in the System.Data.
DataSetExtensions assembly (found in the System.Data.DataSetExtensions.dll library file).
The assembly defines two classes, DataTableExtensions and DataRowExtensions, that include
new extension methods for the DataTable and DataRow classes, respectively. For data tables,
there is a new AsQueryable method that acts as the gateway for bringing ADO.NET data into
a LINQ query.
Dwonloaded from: iDATA.ws
306
On the DataRow side, a new Field(Of T) method moves each data column value from a ge-
neric, semi-typeless existence to a strongly typed presence within your queries. It is still up
to you, as the programmer, to correctly indicate the type of each field as you add them to
the LINQ query syntax. But once defined, you can apply all the standard operators to those
fields, including them in projections, filters, and other types of expressions.
Note
You can use DataRow values within LINQ queries without applying the Field(Of T) exten-
sion method. However, these fields will still pose as System.Object instances. This might prevent
you from carrying out certain types of query actions on specific fields. Also, you must still resolve
the data type of each field before using it in post-query processing.
LINQ to DataSet also lets you craft queries that use ADO.NET Typed Data Sets; however, the
Entity Framework supercedes most of the advantages of typed data sets. Therefore, LINQ queries
against typed data sets are not discussed in this book.
Writing Queries with LINQ to DataSet
With the exception of the new enumerated methods specific to LINQ to DataSet, using
ADO.NET DataTable objects in LINQ queries is identical to using standard collection objects.
The first step involves converting a data table to its enumerable equivalent using the and,
which can be applied to any DataTable instance.
C#
// ----- Customer is an existing DataTable instance.
var results = from cu in Customer.AsEnumerable()
select cu;
Visual Basic
' ----- Customer is an existing DataTable instance.
Dim results = From cu In Customer.AsEnumerable()
Select cu
Although the LINQ to DataSet provider includes “DataSet” in its name, the focus in LINQ
queries is on the DataTable class. LINQ to DataSet does not consider a DataTable instance’s
presence in an overall DataSet to be significant, nor does it examine any DataRelationship
objects when processing queries that contain multiple DataTable instances. You must link
tables together using LINQ’s standard Join operator or use the Where clause to create an
implicit join.
Dwonloaded from: iDATA.ws
Chapter 18 Using LINQ to DataSet
307
C#
// ----- Explicit join.
var results = from cu in Customer.AsEnumerable()
join ord in Order.AsEnumerable() on cu.ID equals ord.CustomerID
select...
// ----- Implicit join
var results = from cu in Customer.AsEnumerable()
from ord in Order.AsEnumerable()
where cu.ID == ord.CustomerID
select...
Visual Basic
' ----- Explicit join.
Dim results = From cu In Customer.AsEnumerable()
Join ord In Order.AsEnumerable() On cu.ID Equals ord.CustomerID
Select...
' ----- Implicit join
Dim results = From cu In Customer.AsEnumerable(), ord In Order.AsEnumerable()
Where cu.ID = ord.CustomerID
Select...
After making the tables part of the query, you can access each row’s individual column values
as if they were typical LINQ query properties. As mentioned previously, LINQ will not auto-
matically ascertain the data type of any given column; you must explicitly cast each field to
its proper type.
To cast a field, add the Field extension method to the end of the range variable (the range
variables in the previous code sample are cu and ord). Because the implementation of Field
uses generics, you must also attach a type name using the language-appropriate syntax. Pass
the name of the column as an argument to Field.
C#
var results = from cu in Customer.AsEnumerable()
orderby cu.Field<string>("FullName")
select new { CustomerName = cu.Field<string>("FullName") };
Visual Basic
Dim results = From cu In Customer.AsEnumerable()
Select CustomerName = cu.Field(Of String)("FullName")
Order By CustomerName
Dwonloaded from: iDATA.ws
308
Microsoft ADO.NET 4 Step by Step
The Field method includes a few overloaded variations. In addition to field names, you can
use a zero-based column position to locate field data, although this might reduce readability
in your queries. An additional argument lets you specify the DataRowVersion to use. By de-
fault, queries use the DataRowVersion.Current version of the row.
Even when enumerated DataTable objects play a key role in a LINQ query, they need not be
the only source of data involved. Part of LINQ’s appeal is that it allows you to write queries
that involve data from disparate sources. You can mix LINQ to Objects and LINQ to DataSet
content in the same query simply by including each source in the From clause.
C#
// ----- Build an ad hoc collection, although you could also
// include a fully realized class.
var statusTable[] = { new { Code = "P", Description = "Active Order" },
new { Code = "C", Description = "Completed / Shipped" },
new { Code = "X", Description = "Canceled" }};
// ----- Link ADO.NET and Object collections in one query.
var results = from ord in Order.AsEnumerable()
join sts in statusTable on
ord.Field<string>("StatusCode") equals sts.Code
orderby ord.Field<long>("ID")
select new { OrderID = ord.Field<long>("ID"),
CurrentStatus = sts.Description };
Visual Basic
' ----- Build an ad hoc collection, although you could also
' include a fully realized class.
Dim statusTable = {New With {.Code = "P", .Description = "Active Order"},
New With {.Code = "C", .Description = "Completed / Shipped"},
New With {.Code = "X", .Description = "Canceled"}}
' ----- Link ADO.NET and Object collections in one query.
Dim results = From ord In Order.AsEnumerable()
Join sts In statusTable On _
ord.Field(Of String)("StatusCode") Equals sts.Code
Select OrderID = ord.Field(Of Long)("ID"),
CurrentStatus = sts.Description
Order By OrderID
As in LINQ to Objects, the actual processing of a LINQ to DataSet query does not occur until
your code references content from a constructed query. However, all the involved DataTable
instances must already be filled with valid data before you make the query. When dealing
Dwonloaded from: iDATA.ws
Chapter 18 Using LINQ to DataSet
309
with data from external sources, you must bring any data you plan to include in a LINQ
query into the relevant DataTable instances before passing the objects through LINQ. If you
use a DataAdapter to load data, call its Fill method before using LINQ to extract data.
Note
The DataAdapter object’s Fill method loads all requested data into local DataSet memory.
If the tables you need to query with LINQ are large and you aren’t able to first reduce the num-
ber of ADO.NET-managed rows, you might wish to consider alternatives to LINQ to DataSet.
LINQ to Entities, discussed in Chapter 19, “Using LINQ to Entities,” can process external data
without the need to load full tables into memory.
Querying with LINQ to DataSet: C#
1. Open the “Chapter 18 CSharp” project from the installed samples folder. The project
includes a Windows.Forms class named OrderViewer, which is a simple order-list viewer.
2. Open the source code view for the OrderViewer form. Locate the GetConnectionString
function; this is a routine that uses a SqlConnectionStringBuilder to create a valid con-
nection string to the sample database. It currently includes the following statements:
sqlPortion.DataSource = @"(local)\SQLExpress";
sqlPortion.InitialCatalog = "StepSample";
sqlPortion.IntegratedSecurity = true;
Adjust these statements as needed to provide access to your own test database.
3. Locate the ActView_Click event handler. This routine displays a list of orders for either
all customers in the database or for a specific customer by ID number. Just after the
“Retrieve all customer orders” comment, add the following statement:
var result = from cu in customerTable.AsEnumerable()
from ord in orderTable.AsEnumerable()
from sts in statusTable
where cu.Field<long>("ID") == ord.Field<long>("Customer")
&& ord.Field<string>("StatusCode") == sts.Code
orderby cu.Field<string>("FullName"), ord.Field<long>("ID")
select new { CustomerID = cu.Field<long>("ID"),
CustomerName = cu.Field<string>("FullName"),
OrderID = ord.Field<long>("ID"),
OrderStatus = sts.Description,
OrderDate = ord.Field<Date>("OrderDate"),
OrderTotal = ord.Field<decimal>("Total") };
This query combines two DataTable instances (customerTable and orderTable, each
decorated with the AsEnumerable extension method) with a collection of local object
instances (statusTable). It forms implicit inner joins between the tables via the where
clause and performs a projection of fields from each source table.
Dwonloaded from: iDATA.ws