LINQ

LINQ, or "Language-Integrated Query", provides a way to perform queries on collections of data that are enumerate, that is, satisfying the IEnumerable interface. These notes cover only some of the features of LINQ.

A LINQ query operations has three parts:

Example:

// Numbers is our data source.
int[] Numbers = new int[7] { 1, 2, 3, 4, 5, 6, 7 };

// We create the query.  NumQuery is the "query variable".  
var NumQuery = 
    from N in Numbers
    where (N % 2) == 0
    select N;
// Here NumQuery, originally a var, will be a list of int.

// We execute the query.
foreach (int M in NumQuery)
{
 Console.WriteLine("{0}", M);
}
// We now have list of even integers from Numbers.

(End of example)

In the above example, we used three keywords: from, where and select, and these are not in the same order in which they would appear in an SQL query.

Creating the query does not by itself execute it. The execution does not occur until we iterate over the query variable using foreach.

Suppose our data is something more complex than an int. For instance, we might have a struct or class called Customer with properties City, LastName and FirstName and perhaps more. If CList is an array or list of Customer, we could have a query:

IEnumerable CQuery =
     from Cust in CList
     where Cust.City == "Dekalb"
     select Cust;

foreach (Customer C in CQuery)
{
 Console.WriteLine(C.Lastname + " " + C.FirstName);
}

Here CQuery is typed as IEnumerable, so we can think of it as a list of Customer instances. The type specified in IEnumerable should match the type of data indicated by "select".

We could instead just use var:

var CQuery =
     from Cust in CList
     where Cust.City == "Dekalb"
     select Cust;

foreach (var C in CQuery)
{
 Console.WriteLine(C.Lastname + " " + C.FirstName);
}

The compiler will deduce that we want IEnumerable.

When we write "from Cust in CList", CList is the data source and Cust is the range variable. The compiler will deduce the type of Cust.

The where clause applies a "filter", that is, it uses a boolean expression to return only elements for which the expression is true. We could have a compound boolean expression such as:

where Cust.City == "Dekalb" && Cust.LastName == "Smith"

or

where Cust.City == "Dekalb" || Cust.LastName == "Smith"

There are more keywords. For instance, we might want the result sorted in some fashion. To arrange for this, we could use orderby:

var CQuery =
     from Cust in CList
     where Cust.City == "Dekalb"
     orderby Cust.LastName Ascending
     select Cust;

We could say "Descending" instead.

Another choice might be to group the results according to the values of a key. Maybe we would like our customers grouped together by City:

var CQuery = 
     from Cust in CList
     group Cust by Cust.City;

This makes CQuery an IGrouping, a list of lists. We could execute it:

foreach (var CustGroup in Cquery)
{
 Console.WriteLine(CustGroup.Key);
 foreach (Customer Cust in CustGroup)
 {
  Console.WriteLine({0}, Cust.LastName);
 }
}

Here the IGrouping interface involves a list in which each element has a key value and a list of elements with that key value.

We could sort the groups by adding "sortby CustGroup.Key Ascending".

Instead of "select Cust", we might be interested only in the names of customers: "select Cust.LastName".