LINQ and the .NET Compact Framework 3.5
- 7.1 Overview
- 7.2 The Sample Application
- 7.3 LINQ to XML
- 7.4 Conclusion
7.1 Overview
WHEN RELATIONAL DATABASE MANAGEMENT SYSTEMS (DBMSs) first burst on the scene in the 1970s one of their capabilities that helped fuel their universal acceptance was SQL, or Structured Query Language. Prior to SQL, all data was retrieved from DBMSs programmatically. For instance:
- Find the record in this file with this value in this column, then walk this chain of records until you find one with that value in that column, then add that record to a temporary file of records. When finished, sort the temporary file.
The (then-new) concept of asking the DBMS for the data you wanted rather than having to program its retrieval was quickly adopted by users and developers alike. Code such as the following quickly became the preferred method for requesting selective, correlated, ordered data from a set of records:
SELECT x.ColA, x.ColB, z.ColC FROM TableX x JOIN TableZ z ON z.ColA = x.ColA WHERE z.ColC = "NS" AND x.ColD > 44 ORDER BY x.ColE DESC, z.ColF
Version 3.5 of .NET—on both the desktop and the device—extends this capability to the client side with Language INtegrated Query (LINQ). LINQ provides the capability for client programs to query sets of objects, such as a Table of DataRows or a Dictionary of Customers or an XML document containing PurchaseOrder elements, by invoking object methods that mirror this SQL functionality. Client-side data can be selected, filtered, correlated, aggregated, and sorted by using LINQ's Standard Query Operators; operators whose names reflect their SQL equivalents—Select, Where, Join, Sum, OrderBy, and many more.
Many desktop programmers see LINQ as a tool for simplifying the access of relational databases. Although this is true for the desktop, it is not, as we will soon explain, true for the .NET Compact Framework. As this chapter and its sample program unfold, the emphasis will be on the great benefit to device applications of LINQ as a manipulator of object sets, be it an array of data objects or a collection of business objects.
The LINQ code to do a select from a set of objects might be as simple as the following:
myList .Where(order => order.CustomerID == "BERGS") .Select(order => order);
The preceding code would extract the BERGS entrants from a collection of objects, each of which had a CustomerId property. Note that these three lines are a single C# statement, one that we could have placed on a single line, were it not for the width limitation of this page.
Also, LINQ provides an alternative syntax that allows you to do the same selection thusly:
from order in myList where order.CustomerID == "BERGS" select order;
Object-oriented programmers, used to the "." notation, will perhaps be most comfortable with the former style, while database programmers will tend toward the latter. For most of this book, we will use the first style simply because we want to emphasize LINQ as a manipulator of object sets, but periodically we will show both, for we do not wish to imply that one style is better than the other.
Although the preceding example was quite simple, a LINQ statement might be as complex as the one shown in Listing 7.1.
Listing 7.1. A Long and Complex LINQ Query
cbxOrders.DisplayMember = "Text"; cbxOrders.ValueMember = "Id"; cbxOrders.DataSource = ( employee.Orders .Where(order => (OrderEntryData.IsActive(order.objectState))) .OrderBy(active => active.parentCustomer.CompanyName) .ThenByDescending(active => active.NetValue) .ThenByDescending(active => active.OrderDate) .Select(sorted => new { Id = sorted.OrderID, Text = sorted.parentCustomer.CustomerID + " - " + sorted.NetValue.ToString("c") .PadLeft(9).Substring(0, 9) + " - " + sorted.OrderDate .ToString("dd-MMM-yy") }) ) .ToList();
The code in Listing 7.1 comes from one of this chapter's sample programs. It does the following:
- Extracts all the active orders from the Orders dictionary object referenced by employee.Orders
- Sorts them into descending order date within descending net value within customer sequence
Converts each order into an object of an unnamed class containing
- An order ID property named Id
- A new property named Text that is the concatenation of the customer ID / net value / order date propertys
- Converts that collection into a List
- Assigns that List to the data source property of a ComboBox such that the OrderId of the order subsequently selected by the user can be easily determined
Listing 7.2 shows the alternative syntax for the statement shown in Listing 7.1.
Listing 7.2. The Same LINQ Query—Alternative Syntax
cbxOrders.DisplayMember = "Text"; cbxOrders.ValueMember = "Id"; cbxOrders.DataSource = (from order in employee.Orders where AppData.IsActive(order.objectState) orderby order.parentCustomer.CompanyName, order.NetValue descending, order.OrderDate descending select new { Id = order.OrderID, Text = String.Format("{0} - {1} - {2}", order.parentCustomer.CustomerID, order.NetValue.ToString("c") .PadLeft(9).Substring(0, 9), order.OrderDate .ToString("dd-MMM-yy")) }).ToList(); }
One look at the code in Listing 7.1 or Listing 7.2 should make anyone who is new to LINQ realize that some additional information about these LINQ methods and their syntax will be necessary before we can begin programming. But we need to start our LINQ programming discussion even further back than LINQ itself. Since LINQ provides for the manipulation of sets of objects, we must be sure that we understand the set classes that are provided with .NET and the specific capabilities of those classes upon which LINQ is built.
7.1.1 Set Classes in .NET
The .NET set class fall into a variety of differing groups, such as arrays versus collections or generic collection classes versus specialty collection classes. LINQ doesn't care what category a set class falls into. What it does need, however, is for the class to have implemented the IEnumerable interface. If the class has implemented IEnumerable, LINQ, like foreach, can work with objects of that class. If the class also implements IList, LINQ may be able to provide performance optimization for certain operators. But the functionality aspect of LINQ is dependent upon the target class having implemented IEnumerable, whether that class is an ADO.NET DataTable, a List<Employee>, or a class of your own design.
The most commonly used general-purpose set classes, the System.Array class and the System.Collections.Generic classes, provide the following capabilities that you will often draw upon when writing your LINQ expressions.
- The items within the set can be restricted to objects of a certain class. Thus, we can speak of "an array of strings" or "a list of customers" and be assured that our array will contain only strings and our list will contain only customers.
- Because, as we mentioned earlier, the set class must implement IEnumerable, the items within the set can be enumerated. That is, they can be accessed one at a time. We often take advantage of enumeration through the use of the foreach statement. Even collection classes that do not provide indexing, such as Queue and Stack, provide enumeration through the IEnumerable interface and thus provide foreach capability.
- The set can determine whether an object is in the set. The testing method may be named Contains or it may be named Exists, but there is always a way to test.
As we write our LINQ code, you will see these common features manifesting themselves within the various LINQ functions that we will be using.
7.1.2 LINQ in the Compact Framework
In .NET 3.5, there are five major variations on LINQ: LINQ to SQL, LINQ to Entities, LINQ to Dataset, LINQ to Objects, and LINQ to XML. The first two, LINQ to SQL and LINQ to Entities, are primarily intended for accessing relational databases and are not available in the Compact Framework. This is why we stated earlier that, in the Compact Framework, LINQ is not primarily a tool for simplifying the access of relational databases, but rather is a manipulator of collections. Since LINQ to SQL and LINQ to Entities are not available in the Compact Framework, we will confine our efforts to the other LINQ variations, notably LINQ to Datasets and LINQ to Objects.
But before starting on each of the three individually, we should make some comments about LINQ syntax in general. In these introductory comments we will be using the word collections in a general sense, a sense that includes both collections and arrays, and definitely not restricting our remarks only to members of the System.Collections.Generic namespace.
As we already mentioned, LINQ methods are generic methods of IEnumerable classes, meaning they work on collections of objects, just as SQL clauses work on the ultimate RDBMS collection, the rows of a table. Like SQL statements, LINQ methods can return a new collection of objects or they can return a single value. In the code in Listing 7.1 Where, OrderBy, and Select each returned a new collection; one that was subsequently refined by the method that followed it. In all, four LINQ methods were invoked, turning the original employee.Orders collection into the List that was data-bound to the ComboBox.
In defining the collection that will be returned from a LINQ operator, such as Where, the => symbol implies that the word preceding the => is a placeholder variable. For each object in the original collection, a reference to that object will be placed into the placeholder variable, the code to the right of the => symbol will be executed, and the result will be used to build the new collection or to compute a value. Thus, both
myList.Where(order => order.CustomerID == "BERGS").Select();
and our old friend foreach, as in
foreach (Order order in employee.Orders) { if (order.CustomerID == "BERGS") { // Add this order to an existing collection. } }
access each order in the employee.Orders collection via the order placeholder variable and use it to generate a new collection.
The => symbol is known by a variety of terms; it is the lambda expression operator in formal LINQ terminology, the anonymous function in the C# specification, and the goes to variable in common usage.
The name of the placeholder variable is up to you. In the complex example shown at the start of this chapter, we used a different name within each of the three methods. We chose order, active, and sorted, respectively, to reflect the progression that was being accomplished. More commonly, a developer would use the same name within each method.
7.1.3 Deferred Execution
Another aspect that makes LINQ unique is that not all of the method calls that comprise the complete transformation need to be declared within a single statement. Thus, the definition of the total LINQ operation can be broken down into separate declarations that will execute as a single operation. For instance, the statement shown earlier,
myList.Where(order => order.CustomerID == "BERGS").Select();
could have been broken into the following:
var bergsCriteria = myList.Where(order => order.CustomerID == "BERGS"); : : : List<Order> bergsOrders = new List<Order>(bergsCriteria.Select(order => order));
The first statement defines the criteria for Berg's orders. The second statement selects all orders meeting the criteria and uses them in the construction of a new list containing just Berg's orders. Note that the first method is not executed at the line where it is declared. Its execution is deferred, becoming part of the execution of the second statement. Therefore, the orders that appear in bergsOrders are the orders that meet the criteria specified in the first statement at the time the second statement is executed. That is, orders that are in myList when the first statement is encountered do not determine the content of bergsOrders; it is the orders that are in myList when the second statement is encountered that determine the contents of bergsOrders.
Also note that bergsCriteria is a variable; its contents can be modified at runtime. The program could have placed a Where method criterion into bergsCriteria, executed the second statement, placed a different Where method criterion into bergsCriteria, executed the second statement again, and produced a completely different collection of orders. This capability to store what is essentially source code into a variable for subsequent execution is very handy.
A query that can be defined across multiple lines of code is referred to as composable. There is a point in time at which a query definition becomes immutable and can no longer be modified. Once a LINQ operator that iterates over the collection, such as Select, has executed, no further composition can occur.
With this background out of the way, we are ready to talk about our coding examples and turn our attention to our sample application, which we use to illustrate both LINQ to Datasets and LINQ to Objects. There are three versions of this application: one that uses data sets, one that uses business objects, and one that combines the benefits of both while requiring the least amount of code.