Chapter 8
LINQ

What’s in This Chapter

  • LINQ query syntax
  • Grouping and aggregating results
  • Creating new LINQ extension methods
  • Method-based queries
  • LINQ to Objects, LINQ to XML, and LINQ to ADO.NET
  • Parallel LINQ

Wrox.com Downloads for This Chapter

Please note that all the code examples for this chapter are available as a part of this chapter’s code download on the book’s website at www.wrox.com/go/csharp5programmersref on the Download Code tab.

Language Integrated Query or LINQ (pronounced “link”) is a data retrieval mechanism that enables programs to filter, select, and group data in much the same way a program can gather data from a database. The difference is that LINQ enables the program to select data from all kinds of data sources, not just databases. LINQ enables a program to select data from arrays, lists, collections, XML data, relational databases, and a wide variety of other data sources.

LINQ enables a program to use a (sort of) natural query language to make complex data selections with little code. For example, suppose you want to search a collection of customer records, find those with balances greater than $50 that are overdue by at least 30 days, and display the list sorted by balance. You could easily write code to loop through the collection to find the appropriate customers and copy them into a new collection. You could then write code to sort that collection. This isn’t terribly hard, but it does take a moderate amount of code that would give you several opportunities to make mistakes.

In contrast, a LINQ query can define this search and sort operation concisely. The program can then execute the query to get the results with little code and little chance to make mistakes. The result may not be as fast as optimized C# code, but LINQ is often much simpler and easier to implement.

LINQ provides dozens of extension methods that apply to all sorts of objects that can hold data such as arrays, dictionaries, and lists. (For more information on extension methods, see the section “Extension Methods” in Chapter 6, “Methods.”) For example, an array of integers by itself is basically just a chunk of memory that holds a bunch of numbers. LINQ adds extension methods to integer arrays that let you perform such tasks as the following:

  • Finding the minimum, maximum, and average value
  • Converting the array into an enumerable list of float or some other data type
  • Concatenating the array with another array
  • Determining whether the array contains a particular value
  • Counting the number of values that meet some condition
  • Returning the first value that meets some condition
  • Returning the distinct values (the values without duplicates)
  • Grouping the values by some criterion
  • Finding the items that are in the array that are also in another array
  • Sorting the items in the array
  • Reversing the order of the items in the array
  • Returning the sum of the items in the array

C# provides a syntax that converts queries into LINQ extension method calls to select, order, and otherwise manipulate the data and return a result.

The standard LINQ tools are divided into the following three broad categories:

  • LINQ to Objects—LINQ methods that interact with C# objects such as arrays, dictionaries, and lists.
  • LINQ to XML—LINQ features that read and write XML data. LINQ lets you easily move data between XML hierarchies and other C# objects.
  • LINQ to ADO.NET—LINQ features that let you write LINQ-style queries to extract data from relational databases.

The following section provides an intuitive introduction to LINQ. Many of LINQ’s details are so complex and technical that they can be quite confusing if they’re stated precisely. However, the basic ideas are mostly straightforward and easy to understand with a few examples. To make things as easy to understand as possible, this chapter relies heavily on examples.

The next section, “Basic LINQ Query Syntax,” describes the most useful LINQ query commands. These enable you to perform queries that select, filter, and arrange data. The section after that, “Advanced LINQ Query Syntax,” describes additional LINQ query commands that you are likely to use less frequently.

The section “Other LINQ Methods” describes methods provided by LINQ but not supported by C#’s LINQ query syntax. To use these methods, you must invoke them as methods for the arrays, dictionaries, lists, and other objects that they extend.

After describing the tools provided by LINQ, most of the rest of the chapter describes the three main categories of LINQ: LINQ to Objects, LINQ to XML, and LINQ to ADO.NET. The chapter finishes by describing Parallel LINQ (PLINQ).

LINQ to Objects is a bit easier to understand than LINQ to XML and LINQ to ADO.NET because it doesn’t require special knowledge beyond C#. To understand LINQ to XML properly, you need to understand XML, which is a complex topic in its own right. Similarly, to get the most out of LINQ to ADO.NET, you need to understand relational databases such as SQL Server, a huge topic about which many books have been written.

Because LINQ to Objects is easiest to cover, this chapter focuses on it, and most of the examples in this chapter use LINQ to Objects. Similar concepts apply to the other forms of LINQ as well. The final sections of the chapter provide some information about LINQ to XML and LINQ to ADO.NET, however, to give you an idea of what is possible in those areas.

Introduction to LINQ

The LINQ extension methods add new features to data stored in various data structures such as arrays and lists. C# provides a higher-level query syntax that makes it easier to use the lower-level extension methods. These higher-level query expressions define the data that should be selected from the data source and the way it should be arranged. LINQ query syntax is somewhat similar to the standard database Structured Query Language (SQL) so it should seem familiar if you have worked with relational databases.

For example, suppose a program defines a Customer class with properties such as Name, Phone, Street, City, State, Zip, Balance, and so forth. Suppose also that the customers array holds all of the application’s Customer objects. Then the following expression defines a query that selects customers with negative account balances. It orders the results by balance in ascending order, so customers with the most negative balances (who owe the most) are listed first.

var overdue =
    from customer in customers
    where customer.Balance < 0
    orderby customer.Balance ascending
    select new { customer.Name, customer.Balance };

Behind the scenes, C# transforms the query expression into calls to the LINQ API and fetches the selected data. The program can then loop through the results as shown in the following code:

foreach (var customer in overdue)
    Console.WriteLine(customer.Name + ": " + customer.Balance);

There are a couple of interesting things to note about this code. First, the previous code fragments do not declare an explicit data type for the overdue query expression or the looping variable customer in the foreach loop. C# automatically infers the data type for both of these variables. If you use a statement such as Console.WriteLine(overdue.GetType().Name) for the overdue and customer variables, you’ll discover that they have the following ungainly type names:

WhereSelectEnumerableIterator`2
<>f__AnonymousType0`2

Because these data types have such awkward names, you don’t want to try to guess them. It’s much easier to use the var data type and let C# figure out the data types for you.

A second interesting fact about this code is that the program doesn’t actually fetch any data when the query expression is defined. It accesses the data source (in this case the customers array) when the code tries to access the result in the foreach loop.

Many programs don’t need to distinguish between when the expression is declared and when it is executed. In this example, if the code iterated through the results right after defining the query, there wouldn’t be much difference. However, if it may be a long time between defining the query and using it or if the query takes a long time to execute, the difference may matter.

Third, if you have any experience with relational databases, you’ll notice that the select clause is in a different position from where it would be in a SQL statement. In SQL the SELECT clause comes first but in LINQ it comes at the end. (This placement is due to issues Microsoft encountered while implementing IntelliSense for LINQ.)

The following sections explain the most useful LINQ keywords supported by C#.

Basic LINQ Query Syntax

The following text shows the typical syntax for a LINQ query:

from ... where ... orderby ... select ...

The following sections describe these four standard clauses. The sections after those describe some of the other most useful LINQ clauses.

from

The from clause tells where the query should get the data and defines the name by which it is known within the LINQ query. Its basic form is

from queryVariable in dataSource

Here queryVariable is a variable that you are declaring to manipulate the items selected from dataSource. This is similar to declaring a looping variable in a for or foreach statement.

You can supply a data type for queryVariable if you know its type; although, because of the anonymous types used by LINQ, it’s often easiest to let LINQ infer the data type automatically. For example, the following query explicitly indicates that the query variable customer is from the Customer class:

var query = from Customer customer in customers select customer.Name;

A query can include multiple from clauses to select values from multiple data sources. For example, the following query selects data from the customers and orders arrays.

var customerOrders =
    from customer in customers
    from order in orders
    select new { customer.Name, order.OrderId };

In database terms, this query returns the cross-product of the two arrays. In other words, it returns every possible {Customer, Order} pair from the two arrays. For every Customer in the customers array, it returns that Customer plus every Order in the orders array.

If the customers array contains Art, Betty, and Carl, and the orders array contains orders numbered 1, 2, and 3, then this query selects the following nine results:

Art        1
Art        2
Art        3
Betty      1
Betty      2
Betty      3
Carl       1
Carl       2
Carl       3

Usually, you include a where clause to connect the objects selected from the two lists. For example, if customers and orders are related by a common CustomerId property, you might use the following query to select customers together with their corresponding orders rather than all orders:

var customerOrders =
    from customer in customers
    from order in orders
    where order.CustomerId == customer.CustomerId
    select new { customer.Name, order.OrderId };

If Art, Betty, and Carl have CustomerId values 1, 2, 3, and the three orders have the corresponding CustomerId values, the preceding query would return the following results:

Art        1
Betty      2
Carl       3

where

The where clause filters the records selected by the from clause. It can include tests involving the objects selected and properties of those objects. The last example in the preceding section shows a particularly useful kind of query that joins objects from two data sources that are related by common property values. The where clause often performs simple tests and comparisons, but it can also execute methods on the selected objects and properties to decide if they should be included in the results.

For example, suppose the Customer class has Balance and PaymentIsLate properties, and suppose the PreferredCustomer class inherits from Customer. Also suppose the customers array contains both Customer and PreferredCustomer objects.

The OwesALot method defined in the following code returns true if a Customer owes at least $50. The query that follows selects objects from customers where the object is not a PreferredCustomer, has a PaymentIsLate value of true, and for which function OwesALot returns true.

private bool OwesALot(Customer customer)
{
    return customer.Balance <= -50m;
}
...
var query =
    from customer in customers
    where !(customer is PreferredCustomer) &&
        customer.PaymentIsLate &&
        OwesALot(customer)
    select customer;

The where clause can include just about any boolean expression, usually involving the selected objects and their properties. As the preceding example shows, it can include !, is, &&, and method calls.

Expressions can use any of the arithmetic, date, string, or other comparison operators. The following query selects Order objects from the orders array where the OrderDate property is after April 1, 2015:

var query =
    from order in orders
    where order.OrderDate > new DateTime(2015, 4, 1)
    select order;

orderby

The orderby clause sorts a query’s results. Usually the values used to sort the results are properties of the objects selected. For example, the following query selects Customer objects from the customers array and sorts them by their Balance properties:

var query =
    from customer in customers
    orderby customer.Balance
    select customer;

This query sorts the customers by their Balance properties in ascending order.

If an orderby clause includes more than one field, the results are sorted by the first value and any ties are broken by the remaining fields. For example, the following query selects customers ordered by Balance. If two customers have the same Balance, they are ordered by LastName. If two customers have the same Balance and LastName, they are ordered by FirstName.

var query =
    from customer in customers
    orderby customer.Balance, customer.LastName, customer.FirstName
    select customer;

To arrange items in descending order, simply add the keyword descending after an ordering expression. Each expression can have its own descending keyword, so you can arrange them independently. For example, you could order customers by Balance ascending, FirstName descending, and LastName descending.

An orderby clause can include calculated values. For example, suppose the Order class has Subtotal, Tax, and Shipping properties. The following query orders its results by the sum of those values.

var query =
    from order in orders
    orderby order.Subtotal + order.Tax + order.Shipping
    select order;

Note that the values used for ordering results are not necessarily the values selected by the query. For example, the preceding query selects all the Order objects in the orders array, not those objects’ total costs. The next section explains the select clause.

select

The select clause lists the fields that the query should select into its result. This can be an entire object taken from a data source or it can be one or more fields taken from multiple data sources. It can include the results of methods or calculations performed on the object’s fields. It can even include more complicated things such as the results of nested queries.

The following query selects the concatenated first and last names of the Customer objects in the customers array.

var query =
    from customer in customers
    orderby customer.FirstName + " " + customer.LastName
    select customer.FirstName + " " + customer.LastName;

If you want to select more than one field from the query’s objects, use the new keyword followed by the values enclosed in braces. For example, the following query selects Customer objects’ concatenated first and last names plus their Balance values.

var query =
    from customer in customers
    orderby customer.FirstName + " " + customer.LastName
    select new
    {
        Name = customer.FirstName + " " + customer.LastName,
        customer.Balance
    };

This technique creates objects of an anonymous type that has properties holding the selected values.

If you select a simple property from a query object, the anonymous type’s property has the same name. In this example, the value customer.Balance is stored in the anonymous object’s Balance property.

If you select a calculated value, you must specify a name for the anonymous type’s property. In this example, the calculated value customer.FirstName + " " + customer.LastName is stored in a property called Name.

Later you can use the anonymous type’s property names when you process the results. The following code shows how a program might display the results from the preceding query.

foreach (var obj in query)
    Console.WriteLine(obj.Name + ": " + obj.Balance);

The queries shown so far return objects of an anonymous type. If you like, you can define a type to hold the results and then create new objects of that type in the select clause. For example, suppose the BalanceInfo class has Name and Balance properties. The following query selects the same data as the preceding query but this time saves the results in new BalanceInfo objects:

var query =
    from customer in customers
    orderby customer.FirstName + " " + customer.LastName
    select new BalanceInfo
    {
        Name = customer.FirstName + " " + customer.LastName,
        Balance = customer.Balance
    };

The result contains BalanceInfo objects instead of objects with an anonymous type. That means the program can use an explicitly typed BalanceInfo object to loop through the result as shown in the following code.

foreach (BalanceInfo info in query)
    Console.WriteLine(info.Name + ": " + info.Balance);

You can also use a class’s constructors to select a new instance of the class. For example, suppose the BalanceInfo class has a constructor that takes a name and account balance as parameters. The following code shows how you could modify the previous query to use that constructor.

var query =
    from customer in customers
    orderby customer.FirstName + " " + customer.LastName
    select new BalanceInfo(
        customer.FirstName + " " + customer.LastName,
        customer.Balance);

Using LINQ Results

A LINQ query expression returns an IEnumerable containing the query’s results. A program can iterate through this result and process the items that it contains.

To determine what objects are contained in the IEnumerable result, you need to look carefully at the select clause. If this clause picks a simple value such as a string or int, the result contains those simple values.

For example, the following query selects customer first and last names concatenated into a single string. The result is a string, so the query’s IEnumerable result contains strings and the foreach loop can treat them as strings.

var query =
    from customer in customers
    select customer.FirstName + " " + customer.LastName;

foreach (string name in query)
    Console.WriteLine(name);

Often the select clause picks some sort of object. The following query selects the Customer objects contained in the customers array. The result contains Customer objects, so the code can use an explicitly typed Customer for its looping variable.

var query =
    from customer in customers
    select customer;

foreach (Customer customer in query)
    Console.WriteLine(customer.FirstName + " " + customer.LastName);

Advanced LINQ Query Syntax

So far this chapter has described basic LINQ commands that you might expect to use regularly, but there’s much more to LINQ than these simple queries. The following sections describe some of the more advanced LINQ commands that are less intuitive and that you probably won’t need to use as often.

join

The join keyword selects data from multiple data sources matching up corresponding fields. The following pseudocode shows the join clause’s syntax:

from variable1 in dataSource1
join variable2 in dataSource2
  on variable1.field1 equals variable2.field2

For example, the following query selects Customer objects from the customers array. For each Customer object, it selects Order objects from the orders array where the two records have the same CustomerId value.

var query =
    from customer in customers
    join order in orders on customer.CustomerId equals order.CustomerId
    select new { customer, order };

join into

You can add an into clause to the join clause to group the joined values into a list with a specified new name. For example, consider the following query. (The new elements are shown in bold.)

var query =
    from customer in customers
    join order in orders on customer.CustomerId equals order.CustomerId
into CustomerOrders
    select new { customer, CustomerOrders };

This query selects Customer and Order data much as the previous query did. This time, however, the Order items selected by the join clause are placed in a list named CustomerOrders. The select clause then selects each Customer object plus its CustomerOrders list.

The following code shows how a program could loop through the results.

foreach (var group in query)
{
    Console.WriteLine(group.customer.FirstName + " " + group.customer.LastName);
    foreach (Order order in group.CustomerOrders)
        Console.WriteLine("    Order: " + order.OrderId);
}

For each group object in the results, the program displays that group’s customer name. It then loops through the group’s CustomerOrders list, displaying each Order object’s OrderId. The following text shows some sample output.

Art Anderson
    Order: 1
    Order: 2
Betty Baker
    Order: 3
Carl Carter

In this example, customer Art Anderson had two orders with OrderId values 1 and 2, Betty Baker had one order with OrderId 3, and Carl Carter had no orders.

group by

Like join into, group by enables a program to gather related values together into groups. It also returns an IEnumerable that holds objects, each containing another IEnumerable.

The following code shows an example.

var query =
    from order in orders
    group order by order.CustomerId;

This query selects Order objects from the orders array. The group order part of the query means you want to select the order objects and you want to group them. The by order.CustomerId part of the query means the objects should be grouped by their CustomerId properties.

The result is a list of objects representing the groups. Each of those objects has a Key property that gives the value that was used to build that group. In this example, the Key is the value of the objects’ CustomerId values.

Each of the objects is also enumerable so the program can loop through the objects that are in its group.

The following code shows how a program could display the results of this query.

foreach (var group in query)
{
    Console.WriteLine("Customer " + group.Key + ":");
    foreach (Order order in group)
        Console.WriteLine("    Order: " + order.OrderId);
}

This code loops over the groups in the query’s results. For each group object, the program displays the group’s Key. It then loops through the group displaying its Order objects.

If you add an into clause after the group by section, you can give the group data a name that you can use later in a select clause. For example, consider the following query.

var query =
    from order in orders
    group order by order.CustomerId into CustomerOrders
    select new { ID = CustomerOrders.Key, Orders = CustomerOrders };

Like the previous example, this query selects Order objects grouped by CustomerId. It gives the grouped data the name CustomerOrders. The select clause then uses that name to select the grouped data’s Key and the grouped data itself.

The following code shows how a program could display the results.

foreach (var group in query)
{
    Console.WriteLine("Customer " + group.ID + ":");
    foreach (Order order in group.Orders)
        Console.WriteLine("    Order: " + order.OrderId);
}

This code is similar to the previous version except it uses the names ID and Orders that the select clause used to name the selected pieces of data.

Aggregate Values

When you group data, you can use aggregate methods to select combined values. For example, suppose you select Order objects grouped by CustomerId so each group in the result contains the orders placed by a single customer. Then you could use the Sum method to calculate the sum of the prices of the orders in each group. The following query shows an example with the Sum method highlighted in bold.

var query =
    from order in orders
    group order by order.CustomerId into Orders
    select new
    {
        ID = Orders.Key,
        Orders,
        TotalPrice = Orders.Sum(order => order.Price)
    };

This query selects Order objects from the orders array, groups them by CustomerId, and gives the name Orders to the groups.

The select clause selects each group’s Key (which is the CustomerId value used to create the group) and the group itself.

For a final selection, the query takes the Orders group and calls its Sum extension method. The Sum method takes as a parameter a method that it should use to select a numeric value from the objects over which it is taking the sum, in this case the Order objects in the group. This example uses a lambda expression that simply returns an Order object’s Price property. (For information on lambda expressions, see the section “Lambda Expressions” in Chapter 6.)

The following code shows how a program can display the results.

foreach (var group in query)
{
    Console.WriteLine("Customer " + group.ID + ": " +
        group.TotalPrice.ToString("C"));
    foreach (Order order in group.Orders)
    {
        Console.WriteLine("    Order " + order.OrderId + ": " +
            order.Price.ToString("C"));
    }
}

This code loops over the groups returned by the query. For each group, it displays the group’s ID (which holds the CustomerId used to build the group) and the group’s TotalPrice (which was calculated by the Sum method). The code then loops through the Order objects that make up the group and displays the objects’ OrderId and Price properties.

The following text shows some sample output.

Customer 1: $41.50
    Order 1: $16.00
    Order 3: $25.50
Customer 2: $13.20
    Order 2: $13.20

The following list summarizes LINQ’s aggregate methods.

  • Aggregate—Uses a method that you specify to perform some calculation on the values
  • Average—Returns the average of the values
  • Count—Returns the number of items that satisfy a condition
  • Sum—Returns the sum of the values
  • LongCount—Returns the number of items that satisfy a condition as a long
  • Max—Returns the maximum value
  • Min—Returns the minimum value

Set Methods

Set methods modify a set of items. For example, the Union method creates the union of two sets. C# does not provide a special syntax for including these methods in a LINQ query, so you must invoke the methods directly. Fortunately these methods are easy to use.

For example, suppose the ordersPaid and ordersDue arrays contain Order objects representing customer orders that have been paid and that are due, respectively. The following statement uses the Union method to create a new array containing all the orders in both arrays.

Order[] allOrders = ordersPaid.Union(ordersDue).ToArray();

The result of the Union method is an iterator that you could loop over with a foreach statement. This example calls the result’s ToArray method to convert it into an array.

You can apply these methods to the results of a query. For example, the following query examines the orders array and selects the Order objects’ CustomerId values. The code then uses the Distinct method to restrict the result to only distinct values. The following foreach statement displays the distinct values.

var query =
    (from order in orders select order.CustomerId).Distinct();
foreach (int id in query)
    Console.WriteLine(id);

The following list summarizes LINQ’s set methods.

Concat—Returns two result sets concatenated

Distinct—Returns a set without duplicates

Except—Returns the items in one set except those that are also in a second set

Intersect—Returns the items that are in both of two set

Union—Returns the items that are in either of two set

Limiting Results

The following list summarizes methods that LINQ provides for limiting the results returned by a query.

  • First—Returns the first result and discards the rest. If the result includes no values, this throws an exception.
  • FirstOrDefault—Returns the first result and discards the rest. If the query contains no results, it returns a default value.
  • Last—Returns the last result and discards the rest. If the result includes no values, this throws an exception.
  • LastOrDefault—Returns the last result and discards the rest. If the query contains no results, it returns a default value.
  • Single—Returns the single item selected by the query. If the query does not contain exactly one result, this throws an exception.
  • SingleOrDefault—Returns the single item selected by the query. If the query contains no results, this returns a default value. If the query contains more than one item, this throws an exception.
  • Skip—Discards a specified number of results and keeps the rest.
  • SkipWhile—Discards results as long as some condition is true and then keeps the rest. (The condition is given by a method, often a lambda expression.)
  • Take—Keeps a specified number of results and discards the rest.
  • TakeWhile—Keeps results as long as some condition is true and then discards the rest.

For example, the following query selects Customer objects in order of increasing Balance property. The expression Take(10) selects the first 10 values and discards the rest. (If the result holds fewer than 10 values, the statement takes them all.) The result is a list of the 10 customers with the smallest balances.

var query =
    (from customer in customers
     orderby customer.Balance
     select customer).Take(10);

The following query uses the TakeWhile method to select all Customer objects with Balance properties less than –50.

var query =
    (from customer in customers
     orderby customer.Balance
     select customer).TakeWhile(customer => customer.Balance < -50m);

Other LINQ Methods

LINQ provides several other extension methods that are not supported by C#’s query syntax. You cannot use them in queries, but you can apply them to the results of queries.

The following list describes some of the more useful remaining LINQ extensions that aren’t supported by C#’s query syntax.

  • Contains—Returns true if the result contains a specific value.
  • DefaultIfEmpty—If the query’s result is not empty, returns the result. If the result is empty, returns an IEnumerable containing a default value.
  • ElementAt—Returns the element at a specific position in the query’s result. If there is no element at that position, this throws an exception.
  • ElementAtOrDefault—Returns the element at a specific position in the query’s result. If there is no element at that position, this returns a default value.
  • Empty—Creates an empty IEnumerable.
  • Range—Creates an IEnumerable containing a range of integer values. For example, Enumerable.Range(100, 5) returns five numbers starting at 100: 100, 101, 102, 103, and 104.
  • Repeat—Creates an IEnumerable containing a value repeated a specified number of times.
  • SequenceEqual—Returns true if two sequences are identical.

LINQ also provides methods that convert results into new data types. The following list summarizes these methods.

  • AsEnumerable—Converts the result into a typed IEnumerable<T>
  • AsQueryable—Converts an IEnumerable into an IQueryable
  • OfType—Removes items that cannot be cast into a specified type
  • ToArray—Returns an array containing the results
  • ToDictionary—Places the results in a Dictionary using a selector function to set each item’s key
  • ToList—Returns a List<T> containing the results
  • ToLookup—Places the results in a System.Linq.Lookup (one-to-many dictionary) using a selector function to set each item’s key

Note that the ToArray, ToDictionary, ToList, and ToLookup functions force the query to execute immediately instead of waiting until the program accesses the results.

LINQ Extension Methods

As was mentioned earlier in this chapter, C# doesn’t actually execute LINQ queries. Instead it converts them into a series of method calls (provided by extension methods) that perform the query. Though C#’s LINQ query syntax is generally easier to use, it is sometimes helpful to understand what those method calls look like.

The following sections explain the general form of these method calls. They explain how the method calls are built, how you can use these methods directly in your code, and how you can extend LINQ to add your own query methods.

Method-Based Queries

Suppose a program defines an array of Customer objects named customers and then defines the following query.

var query =
    from customer in customers
    where customer.Balance < 0
    orderby customer.Balance
    select new
    {
        CustName = customer.FirstName + " " + customer.LastName,
        CustBalance = customer.Balance
    };

This query finds customers that have Balance less than zero, orders them by Balance, and returns a result that can enumerate their names and balances.

To perform this selection, C# converts the query into a series of function calls to form a method-based query that performs the same tasks as the original query. For example, the following method-based query returns roughly the same results as the original LINQ query:

var query =
    customers.Where(OwesMoney).
    OrderBy(OrderByAmount).
    Select(MakeObject);

This code calls the customers list’s Where method. It passes that method the OwesMoney method, which returns true if a Customer object has a negative account balance.

The code then calls the OrderBy method of the result returned by Where. It passes OrderBy the OrderByAmount method, which returns a decimal value that OrderBy can use to order the results of Where.

Finally, the code calls the Select method of the result returned by OrderBy. It passes Select the MakeObject method. That method creates a CustInfo object that has CustName and CustBalance properties.

The exact series of method calls generated by C# to evaluate the LINQ query is somewhat different from the one shown here. The version shown here uses the OwesMoney, OrderByAmount, and MakeObject methods defined in the program to help filter, order, and select data. The method-based query generated by C# uses automatically generated anonymous types and lambda expressions, so it is much uglier.

The following code shows the OwesMoney, OrderByAmount, and MakeObject methods.

// Return true if this Customer has Balance < 0.
private bool OwesMoney(Customer customer)
{
    return customer.Balance < 0;
}

// Return the Customer's balance. This is used to order results.
private decimal OrderByAmount(Customer customer)
{
    return customer.Balance;
}

// A class to hold selected Customer information.
class CustInfo 
{
    public string CustName { get; set; }
    public decimal CustBalance{ get; set; }
}

// Create a new CustInfo object.
private CustInfo MakeObject(Customer customer, int index)
{
    return new CustInfo()
    {
        CustName = customer.FirstName + customer.LastName,
        CustBalance = customer.Balance
    };
}

This code defines the methods that are passed as parameters to the query’s Where, OrderBy, and Select calls, but where are Where, OrderBy, and Select defined? They are called as if they are methods provided by customers, but customers is simply an array of Customer objects and it doesn’t define those methods.

It turns out that Where, OrderBy, and Select are extension methods added to the IEnumerable interface by the LINQ library. Arrays implement that interface so they gain these extension methods.

Similarly, LINQ adds other extension methods to the IEnumerable interface such as Any, All, Average, Count, Distinct, First, GroupBy, OfType, Repeat, Sum, Union, and many more.

Method-Based Queries with Lambda Functions

Lambda expressions make building method-based queries somewhat easier. When you use lambda expressions, you don’t need to define separate methods to pass as parameters to LINQ methods such as Where, OrderBy, and Select. Instead, you can pass a lambda expression directly into the method.

The following code shows a revised version of the previous method-based query. Here the method bodies have been included as lambda expressions.

var query =
    customers.Where(customer => customer.Balance < 0).
    OrderBy(customer => customer.Balance).
    Select(customer => new CustInfo()
        {
            CustName = customer.FirstName + " " + customer.LastName,
            CustBalance = customer.Balance
        }
    );

This is more concise because it doesn’t require you to build separate methods, but it can be a lot harder to read and understand. Passing a simple lambda expression to the Where or OrderBy method may not be too confusing, but if you need to perform complex tests, you may be better off making separate methods.

Whether you use methods or lambda expressions, the standard LINQ query syntax is usually easier to understand, so you may prefer to use that version whenever possible. Unfortunately, many references describe the LINQ extension methods as if you are going to use them in method-based queries rather than in LINQ queries. For example, the description of the OrderBy method at msdn.microsoft.com/library/bb534966.aspx includes the following definition:

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
        this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector
)

This declaration is quite confusing, but you can figure it out of you must. In this case the declaration means the following:

  • The method is named OrderBy.
  • It takes two generic type parameters: TSource and TKey.
  • The method’s return value has type IOrderedEnumerable<TSource>.
  • The method extends IEnumerable<TSource>.
  • The method takes as a parameter a Func<TSource, TKey>.

For more information on extension methods, see the section “Extension Methods” in Chapter 6. For more information on generics, see Chapter 15.

As previously mentioned, C#’s LINQ query syntax is usually easier to understand. One time when you need to use the more confusing method-style syntax is when you want to add your own LINQ extensions. The following section explains how you can write extension methods to add new features to LINQ.

Extending LINQ

LINQ queries return some sort of IEnumerable object. (Actually, they return some sort of SelectIterator creature but the result implements IEnumerable.) The items in the result may be simple types such as int, string, or Customer objects, or they may be of some bizarre anonymous type that groups several selected fields together. Whatever the items are, the result is some sort of IEnumerable.

Because the result is an IEnumerable, you can add new methods to the result by creating extension methods for IEnumerable.

For example, the following code defines a standard deviation function. It extends the IEnumerable<decimal> interface so it applies to the results of a LINQ query that fetches decimal values.

public static class MyLinqExtensions
{
    // Return the standard deviation of
    // the values in an IEnumerable<decimal>.
    public static decimal StdDev(this IEnumerable<decimal> source)
    {
        // Get the total.
        decimal total = source.Sum();

        // Calculate the mean.
        decimal mean = total / source.Count();

        // Calculate the sums of the deviations squared.
        var deviationsSquared =
            from decimal value in source
            select (value - mean) * (value - mean);
        decimal totalDeviationsSquared = deviationsSquared.Sum();
        // Return the standard deviation.
        return (decimal)Math.Sqrt((double)
            (totalDeviationsSquared / (source.Count() - 1)));
    }
}

Now the program can apply this method to the result of a LINQ query that selects decimal values. The following code uses a LINQ query to select Balance values from the customers array where the Balance is less than zero. It then calls the StdDev extension method and displays the result.

var query =
    from customer in customers
    where customer.Balance < 0
    select customer.Balance;
Console.WriteLine(query.StdDev());

The following code performs the same operations without storing the query in an intermediate variable:

Console.WriteLine(
    (from customer in customers
     where customer.Balance < 0
     select customer.Balance).StdDev());

LINQ to Objects

LINQ to Objects refers to methods that let a program extract data from objects that are extended by LINQ extension methods. These methods extend IEnumerable so that they apply to any class that implements IEnumerable including arrays, Dictionary, HashSet, LinkedList, Queue, SortedDictionary, SortedList, Stack, and others.

All the examples shown previously in this chapter use LINQ to Objects, so this section says no more about them. See the previous sections for more information and examples.

LINQ to XML

LINQ to XML refers to methods that let a program move data between XML objects and other data-containing objects. For example, using LINQ to XML you can select customer data from arrays and use it to build an XML document. Chapter 24 explains general ways a C# program can manipulate XML data. The sections that follow describe LINQ to XML methods.

LINQ comes with its own assortment of XML elements. These classes, which are contained in the System.Xml.Linq namespace, correspond to similar classes in the System.Xml namespace; although their names begin with X instead of Xml. For example, the System.Xml.Linq.XElement class corresponds to System.Xml.XmlElement.

The LINQ versions of the XML classes provide many of the same features as the System.Xml versions, but they also provide support for LINQ features.

The following section explains how you can easily include XML data in your program. The two sections after that describe methods for using LINQ to move data into and out of XML objects.

XML Literals

C# does not support XML literals so you cannot type XML data directly into your program. You can, however, pass an XML object’s Parse method a string containing XML data. If you prefix the string with the @ character, the string can even span multiple lines.

For example, the following code creates an XElement object containing an <Employees> element that holds three <Employee> elements.

// Read the XElement.
XElement xelement = XElement.Parse(
@"<Employees>
    <Employee FirstName=""Ann"" LastName=""Archer""/>
    <Employee FirstName='Ben' LastName='Baker'/>
    <Employee>
      <FirstName>Cindy</FirstName>
      <LastName>Cant</LastName>
    </Employee>
  </Employees>
");

The three <Employee> elements demonstrate three ways to give the elements FirstName and LastName values. The first uses two sets of double quotes to represent quotes in the data. When the C# compiler sees two pairs of double quotes inside a quoted string, it places a single double quote in the string’s data.

The second <Employee> delimits its name values with single quotes. XML data can use single or double quotes to delimit values. Using single quotes makes the C# code a lot easier to read than using pairs of double quotes.

The first two <Employee> elements hold their FirstName and LastName values in attributes. The third <Employee> holds its values in sub-elements.

Building the same XML hierarchy by using System.Xml objects would take a lot more work. You would need to write code to create the <Employees> element. Then you would need to write code to create each of the <Employee> elements. You would need to add code to set the first two <Employees> elements’ FirstName and LastName properties, and you would need to add code to create the final <Employee> element’s FirstName and LastName child elements.

This is all reasonably straightforward but it is cumbersome. Parsing a string is much easier. Because the string shows the XML data’s structure, parsing is also more intuitive.

LINQ into XML

LINQ’s XML classes provide constructors that let you build XML documents relatively easily. Each constructor’s parameter list ends with a parameter array so you can pass any number of items into it.

The constructors also know what to do with parameters of different types. For example, if you pass an XElement into another XElement’s constructor, then the first element becomes a child of the second. If you pass an XAttribute object into an XElement’s constructor, then the XAttribute object becomes an attribute of the XElement.

This lets you build XML structures in a fairly intuitive manner. The following code shows how you could use constructors to build the previous XML fragment.

XElement employees = new XElement("Employees",
    new XElement("Employee",
        new XAttribute("FirstName", "Ann"),
        new XAttribute("LastName", "Archer")
    ),
    new XElement("Employee",
        new XAttribute("FirstName", "Ben"),
        new XAttribute("LastName", "Baker")
    ),
    new XElement("Employee",
        new XElement("FirstName", "Cindy"),
        new XElement("LastName", "Cant")
    )
);

The code starts by creating the <Employees> element. It passes that object’s constructor three <Employee> XElement objects so they become its children in the XML document.

The code passes the constructors for the first and second <Employee> elements two XAttribute objects so those objects become attributes of the elements.

The third <Employee> element’s constructor takes as parameters two additional XElement objects so that element stores its FirstName and LastName values as separate elements.

This technique of building an XML document by using constructors is called functional construction.

The result is the same as the result given by parsing XML text in the previous section. Figure 8-1 shows a message box displaying the results of the employees object’s ToString method.

c08f001.tif

Figure 8-1: Functional construction enables you to build XML documents relatively easily and intuitively.

Functional construction is reasonably straightforward, but it’s still not quite as easy as parsing XML text, as demonstrated in the previous section. Functional construction does offer one advantage, however. If you pass an XML class’s constructor an object that implements IEnumerable, the constructor enumerates it and adds all the items it contains to the XML hierarchy in an appropriate manner. That means you can use a LINQ query to create pieces of the XML structure.

For example, suppose a program has an array named employees that contains Employee objects. The following code uses LINQ and functional construction to build an XML fragment containing elements for each of the Employee objects.

// Use LINQ to create a list of <Employee> elements.
var makeEmployees =
    from employee in employees
    select new XElement("Employee",
        new XAttribute("FirstName", employee.FirstName),
        new XAttribute("LastName", employee.LastName));

// Create the XML document.
XElement document = new XElement("Employees", makeEmployees);

The code starts with a LINQ query that selects information from the employees array. For each Employee in the array, the query creates an XElement. It uses the Employee object’s FirstName and LastName properties to create XAttribute objects for the XElement.

You can even include the LINQ query directly inside the top-level constructor, as shown in the following code.

XElement document2 = new XElement("Employees",
    from employee in employees
    select new XElement("Employee",
        new XAttribute("FirstName", employee.FirstName),
        new XAttribute("LastName", employee.LastName)));

LINQ out of XML

The LINQ XML objects provide a standard assortment of LINQ methods that make moving data from those objects into IEnumerable objects simple. Using these functions, it’s about as easy to select data from the XML objects as it is from IEnumerable objects such as arrays and lists.

XML objects represent hierarchical data. To make using that data easier, the XML classes also provide methods to help you search those data hierarchies. For example, the XElement object provides a Descendants method that searches the object’s descendants for elements of a certain type.

For example, the following code searches the XElement named document for descendants named “Employee” and displays their FirstName and LastName attributes.

var selectEmployee =
    from employee in document.Descendants("Employee")
    select new
    {
        FirstName = employee.Attribute("FirstName").Value,
        LastName = employee.Attribute("LastName").Value
    };
foreach (var obj in selectEmployee)
    Console.WriteLine(obj.FirstName + " " + obj.LastName);

The LINQ query selects objects from document.Descendants("Employee"). Each of the objects returned by the Descendants method is an XElement. The query uses that object’s Attribute method to get the object’s FirstName and LastName attributes. Those attributes are XAttribute objects, so the code uses their Value properties to get the attribute values. Finally, the query creates a new object of an anonymous type holding the FirstName and LastName attribute values.

The following table describes other methods supported by XElement that a program can use to navigate through an XML hierarchy. Most of these methods return IEnumerable objects that you can use in LINQ queries.

FunctionReturns
AncestorsIEnumerable containing all ancestors of the element.
AncestorsAndSelfIEnumerable containing this element followed by all of its ancestors.
AttributeThe element’s attribute with a specific name.
AttributesIEnumerable containing the element’s attributes.
DescendantsIEnumerable containing all descendants of the element.
DescendantsAndSelfIEnumerable containing this element followed by all of its descendants.
DescendantNodesIEnumerable containing all descendant nodes of the element. These include all nodes such as XElement and XText.
DescendantNodesAndSelfIEnumerable containing this element followed by all its descendant nodes.
ElementThe first child element with a specific name.
ElementsIEnumerable containing the immediate children of the element.
ElementsAfterSelfIEnumerable containing the siblings of the element that come after this element.
ElementsBeforeSelfIEnumerable containing the siblings of the element that come before this element.
NodesIEnumerable containing the nodes that are immediate children of the element. These include all nodes such as XElement and XText.
NodesAfterSelfIEnumerable containing the sibling nodes of the element that come after this element.
NodesBeforeSelfIEnumerable containing the sibling nodes of the element that come before this element.

Most of the methods that return an IEnumerable take an optional parameter that indicates the names of the elements to select. For example, if you pass the Descendants function the parameter “Customer,” the function returns only the descendants of the element that are named “Customer.”

LINQ to ADO.NET

LINQ to ADO.NET provides tools that let you apply LINQ-style queries to objects used by ADO.NET to store and interact with relational data. LINQ to ADO.NET includes three components: LINQ to SQL, LINQ to Entities, and LINQ to DataSet. The following sections briefly give additional details about these three pieces.

LINQ to SQL and LINQ to Entities

LINQ to SQL and LINQ to Entities are object-relational mapping (O/RM) tools that build strongly typed classes for modeling databases. They generate classes to represent the database and the tables that it contains. LINQ features provided by these classes allow a program to query the data model objects.

For example, to build a database model for use by LINQ to SQL, select Project ⇒ Add New Item command and add a new LINQ to SQL Classes item. This opens a designer where you can define the database’s structure.

If you have SQL Server installed and running, you can drag SQL Server database objects from the Server Explorer to build the database model. If you drag all the database’s tables onto the designer, you should see all the tables and their fields, primary keys, relationships, and other structural information. (Alternatively, you can use the designer’s tools to build the data model yourself instead of building a model from the database’s structure.)

As you create the data model, LINQ to SQL creates corresponding classes to represent the database and its tables. For example, it defines a class that inherits from DataContext to represent the database. If you named the data model SalesInfo, LINQ to SQL defines the class SalesInfoDataContext to represent the database.

Now suppose the program creates an instance of that class named db. Then the following code selects all the records from the database’s Customers table ordered by name:

var query =
    from customer in db.Customers
    orderby customer.FirstName, customer.LastName
    select new { customer.FirstName, customer.LastName };

Microsoft intends LINQ to SQL to be a tool for quickly building LINQ-enabled classes for use with SQL Server databases. The designer can take a SQL Server database, build a model for it, and then create the necessary classes.

LINQ to Entities provides support for writing queries against Entity Framework data models. The Entity Framework is intended for use in more complicated enterprise scenarios than LINQ to SQL. It allows extra abstraction that decouples a data object model from the underlying database. For example, the Entity Framework allows you to store pieces of a single conceptual object in more than one database table.

Building and managing SQL Server databases and the Entity Framework are topics too large to cover in this book so LINQ to SQL and LINQ to Entities are not described in more detail here. For more information, consult the online help or Microsoft’s website. Some of Microsoft’s relevant web pages include:

LINQ to DataSet

LINQ to DataSet lets a program use LINQ-style queries to select data from DataSet objects. A DataSet contains an in-memory representation of data contained in relational tables.

A DataSet can hold data and provide query capabilities whether the data was loaded from SQL Server, from some other relational database, or by the program’s code.

The DataSet object itself doesn’t provide many LINQ features. It is mostly useful because it holds DataTable objects that represent groupings of items, much as IEnumerable objects do.

The DataTable class does not directly support LINQ either, but it has an AsEnumerable method that converts the DataTable into an IEnumerable, which you already know supports LINQ.

The LinqToDataSetScores example program, which is available for download on the book’s website, demonstrates several LINQ to DataSet techniques. This program builds a DataSet that holds two tables. The Students table has fields StudentId, FirstName, and LastName. The TestScores table has fields StudentId, TestNumber, and Score. The tables’ StudentId fields provide the link between the two tables.

The program uses the following code to get references to the DataTable objects that represent the tables.

// Get references to the tables.
DataTable studentsTable = testScoresDataSet.Tables["Students"];
DataTable scoresTable = testScoresDataSet.Tables["TestScores"];

The program then uses the following code to select the names of students with LastName before “F” alphabetically:

var namesBeforeFQuery =
    from student in studentsTable.AsEnumerable()
    where (student.Field<string>("LastName").CompareTo("F") < 0)
    orderby student.Field<string>("LastName")
    select new
    {
        FirstName = student.Field<string>("FirstName"),
        LastName = student.Field<string>("LastName")
    };
namesBeforeDDataGrid.DataSource = namesBeforeFQuery.ToList();

There are only a few differences between this query and previous LINQ queries. First, the from clause calls the DataTable object’s AsEnumerable method to convert the table into something that supports LINQ.

Second, the syntax student.Field<string>("FirstName") lets the query access the LastName field in the student object. (The student object is a DataRow within the DataTable.)

Finally, the last line of code in this example sets a DataGrid control’s DataSource property equal to the result returned by the query to make the control display the results. The DataGrid control cannot display the IEnumerable result. so the code calls the ToList method to convert the result into a list, which the DataGrid can use.

The following list summarizes the key differences between a LINQ to DataSet query and a normal LINQ to Objects query:

  • The LINQ to DataSet query must use the DataTable object’s AsEnumerable method to make the object queryable.
  • The code can access the fields in a DataRow, as in student.Field<string>("LastName").
  • If you want to display the results in a bound control such as a DataGrid or ListBox, use the query’s ToList method.

If you understand these key differences, the rest of the query is similar to those used by LINQ to Objects. The following code shows a second query demonstrated by the program:

// Select all students and their scores.
var allScoresQuery =
    from student in studentsTable.AsEnumerable()
    join score in scoresTable.AsEnumerable()
        on student.Field<int>("StudentId") equals score.Field<int>("StudentId")
    orderby student.Field<int>("StudentId"), score.Field<int>("TestNumber")
    select new
    {
        ID = student.Field<int>("StudentId"),
        Name = student.Field<string>("FirstName") + " " +
            student.Field<string>("LastName"),
        Test = score.Field<int>("TestNumber"),
        Score = score.Field<int>("Score")
    };
allScoresDataGrid.DataSource = allScoresQuery.ToList();

This query selects records from the Students table, joins them with the corresponding records in the TestScores table, and orders the results by student ID and test number. It selects student ID, first and last names, test number, and score. It then displays the result of the query in the allScoresDataGrid control.

The following code shows a more complicated example.

// Make a function to convert a numeric grade to a letter grade.
Func<double, string> letterGrade = score =>
{
    if (score >= 90) return "A";
    if (score >= 80) return "B";
    if (score >= 70) return "C";
    if (score >= 60) return "D";
    return "F";
};

// Display names, averages, and grades for A students.
var aStudents =
    from student in studentsTable.AsEnumerable()
    join score in scoresTable.AsEnumerable()
        on student.Field<int>("StudentId") equals score.Field<int>("StudentId")
    group score by student into studentGroup
    where studentGroup.Average(s => s.Field<int>("Score")) >= 90
    orderby studentGroup.Average(s => s.Field<int>("Score")) descending
    select new
    {
        Name = studentGroup.Key.Field<string>("FirstName") + " " +
               studentGroup.Key.Field<string>("LastName"),
        Average = studentGroup.Average(s => s.Field<int>("Score")),
        Grade = letterGrade(studentGroup.Average(s => s.Field<int>("Score")))
    };
aStudentsDataGrid.DataSource = aStudents.ToList();

This code starts by defining a Func<double, string> delegate and setting it equal to a statement lambda that converts a numeric grade into a letter grade.

Next, the code defines a query that selects corresponding records from the Students and TestScores tables. It groups the records by student, so the records for a particular student are gathered in a group called studentGroup.

The where clause uses the studentGroup’s Average method to calculate the average of the Score values in the group. (The items in studentGroup are the TestScore records for a student. This statement takes the average of the Score fields in those TestScore objects.) The where clause then picks the records where the average of the student’s test scores is at least 90.

The orderby clause orders the results by the students’ average scores.

Finally, the select clause selects the students’ first and last names, average test score, and average test score converted into a letter grade.

The snippet finishes by displaying the results in the aStudentsDataGrid control. Figure 8-2 shows the program displaying this information.

c08f002.tif

Figure 8-2: Example program LinqToDataSetScores displays (among other things) name, test score average, and letter grade for A students.

LINQ to DataSet not only allows you to pull data out of a DataSet but also provides a way to put data into a DataSet. If the query selects DataRow objects, then the query’s CopyToDataTable method converts the query results into a new DataTable object that you can then add to a DataSet. The following code demonstrates this technique.

// Make a new table.
var newTableQuery =
    from student in studentsTable.AsEnumerable()
    where student.Field<string>("LastName").CompareTo("D") < 0
    select student;
DataTable newDataTable = newTableQuery.CopyToDataTable();
newDataTable.TableName = "NewTable";
testScoresDataSet.Tables.Add(newDataTable);
newTableDataGrid.DataSource = newDataTable;

This code selects records from the Students table for students with last name that come before “D” alphabetically. It then uses CopyToDataTable to convert the result into a DataTable. It gives the table a name and adds it to the DataSet. It finishes by displaying the results in the newTableDataGrid control.

PLINQ

Parallel LINQ (PLINQ pronounced “plink”) allows a program to execute LINQ queries across multiple processors or cores in a multicore system. If you have multiple cores or CPU’s and a nicely parallelizable query, PLINQ may improve your performance considerably.

So what kinds of queries are “nicely parallelizable?” The short, glib answer is, “It doesn’t really matter.” Microsoft has gone to great lengths to minimize the overhead of PLINQ, so using PLINQ may help for some queries and shouldn’t hurt you too much for queries that don’t parallelize nicely.

Simple queries that select items from a data source often parallelize well. If the items in the source can be examined, selected, and otherwise processed independently, then the query is parallelizable.

Queries that must use multiple items at the same time do not parallelize as efficiently. For example, adding an orderby clause to the query forces the program to gather all the results and sort them so that part of the query at least will not benefit from PLINQ.

Adding parallelism to LINQ is remarkably simple. Simply add a call to AsParallel to the enumerable object that you’re searching. The FindPrimes example program, which is available for download on the book’s website, uses PLINQ to find prime numbers. The program uses the following IsPrime method.

private static bool IsPrime(int number)
{
    if (number % 2 == 0) return false;
    for (int i = 3; i * i <= number; i += 2)
        if (number % i == 0) return false;
    return true;
}

The program uses the IsPrime method in the following PLINQ query. The call to AsParallel is highlighted in bold.

var primes =
    from number in Enumerable.Range(2, maxNumber).AsParallel()
    where IsPrime(number)
    select number;

The program times this query with and without the call to AsParallel. On my dual-core system, this query without AsParallel takes approximately 12.38 seconds to find all the primes between 2 and 10 million. Using PLINQ the program takes only 7.34 seconds. This is more than one-half the time for the nonparallel version because there’s some overhead in managing the parallel threads. It’s still a nice speed improvement for little extra typing.

Summary

LINQ lets you perform SQL-like queries in C# code. Depending on which form of LINQ you use, the development environment may provide strong type checking and IntelliSense support.

LINQ to Objects lets you query arrays, lists, and other objects that implement the IEnumerable interface.

LINQ to XML and the LINQ XML classes let you use LINQ to extract data from XML hierarchies. You can generate XML documents by parsing text or by using functional construction.

LINQ to ADO.NET (which includes LINQ to SQL, LINQ to Entities, and LINQ to DataSet) allows a program to perform queries on objects representing data in a relational database. Together these LINQ tools allow a program to select data in powerful new ways.

If you have a multicode system, PLINQ can sometimes speed up your LINQ queries with little effort on your part.

For much more information on the various LINQ technologies, see the online help and the web. The following list includes several useful Microsoft web pages that you can visit to learn more about LINQ. Some are a bit old but they still provide valuable information.

The LINQPad tool available at www.linqpad.net helps you interactively write LINQ queries and can execute them against a database without compiling a program. It comes in free, pro, and premium editions.

Using the C# statements and techniques described in Chapters 4 through 8, you can build applications that are extremely powerful and complex. Actually, you can build applications that are so complex it’s hard to ensure that they work correctly. Even a relatively simple application can run into problems and large applications are practically guaranteed to contain bugs. Chapter 9, “Error Handling,” explains how you can protect an application from unexpected errors and let it take action to correct problems, or at least to avoid crashing.

Exercises

  1. Write a program that uses functional construction to build an XElement representing the following XML volleyball league data.
    <League Night='Wednesday'>
        <Teams>
            <Team Name='Bling'>
                <Players>
                    <Player FirstName='Anthony' LastName='Bell' />
                    <Player FirstName='Jacqueline' LastName='Walker' />
                </Players>
            </Team>
            <Team Name='Flying Dolphins'>
                <Players>
                    <Player FirstName='Steve' LastName='Foster' />
                    <Player FirstName='Phyllis' LastName='Henderson' />
                </Players>
            </Team>
        </Teams>
        <Matches>
            <Match>
                <Team Name='Bling' Score='25'/>
                <Team Name='Flying Dolphins' Score='19'/>
            </Match>
        </Matches>
    </League>

    If root is the name of the root XElement, display the data by using the statement Console.WriteLine(root.ToString()).

  2. The VolleyballTeams example program has an XmlString method that returns a string containing XML data similar to the data shown in Exercise 1 but with more teams, players, and match data. Download that program and modify it so it parses the data to build an XML hierarchy and displays the result.
  3. Copy the program you build for Exercise 2 and modify it to use LINQ to XML to display the teams and their players. The result should look like the following:
    Bling
        Anthony Bell
        Jacqueline Walker
        ...
    Flying Dolphins
        Steve Foster
        Phyllis Henderson
        ...
    ...
  4. Copy the program you build for Exercise 2 and modify it to use LINQ to XML to display the teams and the numbers of points they won in each of their matches. The result should look like the following:
    Team                    Points
    ====                    ======
    Bling                       25
    Bling                       25
    Bling                       14
    Bling                       19
    Bling                       25
    Flying Dolphins             19
    Flying Dolphins             25
    Flying Dolphins             22
    Flying Dolphins             14
    Flying Dolphins             19
    ...
  5. Copy the program you build for Exercise 4 and modify it to display each team’s total number of wins (assume a team wins if it got 25 points in a match) and total number of points. Order the results by total wins followed by total points, both descending so the best teams come first. The result should look like the following:
    Name                      Wins    Points
    ====                      ====    ======
    Sand Crabs                   4       119
    Golden Spikers               4       117
    Bling                        3       108
    The Wee Free People          2        95
    Flying Dolphins              1        99
    Hurricanes                   1        95
  6. Copy the program you build for Exercise 2 and modify it to use LINQ to DataSet to create a volleyball league DataSet. The DataSet should hold three DataTables with the following structure:
    Teams
        TeamName        string
    Players
        FirstName       string
        LastName        string
        TeamName        string
    Matches
        TeamName        string
        VersusTeamName  string
        Score           int
  7. Copy the program you build for Exercise 6. Modify it to repeat Exercise 3 but using LINQ to DataSet instead of LINQ to XML.
  8. Copy the program you build for Exercise 6. Modify it to repeat Exercise 4 but using LINQ to DataSet instead of LINQ to XML.
  9. Copy the program you build for Exercise 6. Modify it to repeat Exercise 5 but using LINQ to DataSet instead of LINQ to XML.
  10. In actual volleyball leagues, teams are ranked by their win percentage (in case teams don’t all play the same number of games). If two teams have the same win percentage, their point differentials (total points “for” minus total points “against”) break any ties, not simply the teams’ total points.

    Copy the program you build for Exercise 9 and modify it so it displays each team’s name, wins, losses, win percentage, points “for,” points “against,” and point differential. Order the results by team rankings.

    The result should look like the following:

    Name                     Won    Lost   Win %    Pts+    Pts-    Diff
    ====                     ===    ====   =====    ====   =====    ====
    Golden Spikers             4       1   80.00     117      88      29
    Sand Crabs                 4       1   80.00     119      97      22
    Bling                      3       2   60.00     108     108       0
    The Wee Free People        2       3   40.00      95     108     -13
    Hurricanes                 1       4   20.00      95     110     -15
    Flying Dolphins            1       4   20.00      99     122     -23
  11. Copy the program you build for Exercise 10. Modify it to insert a Standings element inside the League XML element. Use LINQ to XML to insert Team elements in the Standings element to record the standings. If root is the root of the XML hierarchy, use Console.WriteLine(root.ToString()) to verify that the result looks like the following:
    <League>
        <Teams>
            ...
        </Teams>
        <Matches>
            ...
        </Matches>
        <Standings>
            <Team Name="Golden Spikers" Wins="4" Losses="1" WinPercent="80"
                PointsFor="117" PointsAgainst="88" PointDifferential="29" />
            <Team Name="Sand Crabs" Wins="4" Losses="1" WinPercent="80"
                PointsFor="119" PointsAgainst="97" PointDifferential="22" />
            <Team Name="Bling" Wins="3" Losses="2" WinPercent="60"
                PointsFor="108" PointsAgainst="108" PointDifferential="0" />
            <Team Name="The Wee Free People" Wins="2" Losses="3" WinPercent="40"
                PointsFor="95" PointsAgainst="108" PointDifferential="-13" />
            <Team Name="Hurricanes" Wins="1" Losses="4" WinPercent="20"
                PointsFor="95" PointsAgainst="110" PointDifferential="-15" />
            <Team Name="Flying Dolphins" Wins="1" Losses="4" WinPercent="20"
                PointsFor="99" PointsAgainst="122" PointDifferential="-23" />
        </Standings>
    </League>
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.47.230