So far in this series, we have seen the same LINQ statements over and over. Let's open things up a bit and see what else LINQ can do.
The power of LINQ is that your code is nearly identical whether you're using LINQ to Entities, LINQ to XML, or LINQ to something else. However, there are subtle differences.
Here's an easy way to find out what your particular LINQ context can do. As I mentioned in the introductory post, I prefer to use LINQ's method syntax rather than the supposedly more user-friendly query syntax that you'll see in most examples on the Internet. One of the big reasons is that the method syntax provides intellisense in Visual Studio. Thus, if I'm querying a Cities collection as you've already seen umpteen times in this series, I can type...
Cities.
...and as soon as I key the period up pops a list of all the possibilities!
If you've gotten as far as...
Cities.Where(
...you'll get intellisense for the parameters. If you right-click on the Where and select Go To Definition, you'll find yourself in a list of all the LINQ extensions methods for your current LINQ provider.
None of that is available if you're using the query syntax!
Aaaaaanyway, here's a query that extends what we've seen earlier while demonstrating a few of the operators you're likely to use most. It's from the Demo5 project in the sample code that goes with this series.
return Cities
.Where(c => c.IsCapital)
.OrderByDescending(c => c.Population)
.Take(numDesired)
.GroupJoin(
States,
c => c.StateCode,
s => s.StateCode,
(city, states) => Tuple.Create(
city.Name + ", " + (states.Count() > 0 ? states.First().Name : "(Unknown)"),
city.Population));
Filtering
The Where method filters like a SQL WHERE clause except that you pass a Lambda expression as a parameter. Here's we're saying that we only want the capital cities.
If you're using LINQ to query an IEnumerable<>, the Lambda is a Func and it can be just about anything. However, if you're using some other LINQ providers (e.g., LINQ to Entities), the Lambda is actually an Expression. Expressions are the subject of the next post, but for now just consider them to be data, not code, even though they look just like Funcs. It is the LINQ provider's job to parse the data. If it can't, you'll probably get a runtime error.
Joining
The GroupJoin method is one of several join-type operators that LINQ provides. It's not as intimidating as it looks.
The first parameter, States, says what we want to join to. In the demo, States is a List<> that contains the states in the USA: their 2-letter state codes as well as their full names.
The next two parameters tell GroupJoin how to obtain the key in each object. In our code, it will join Cities to States by the StateCode property in each. The GroupJoin actually gives us an IEnumerable of States for each City. That will be useful in the next parameter.
The final parameter is a Lambda that indicates what we want to obtain out of each joined City and IEnumerable<State>. If the IEnumerable<State> (i.e., the states parameter of the Lambda) contains something, we'll select the first state's name. (Of course, there should only be at most one state). If it does not (Count is 0), we select "(Unknown)" as a default. We also select the city's population, and package everything up in a Tuple.
The result is an IEnumerable of Tuples, each of which contains something like "Hartford, Connecticut" in its Item1 and a population in Item2.
Projection
There's another very important method, which I found confusing at first. It's called Select and what's confusing is the way it's sometimes described as "projecting" a new sequence. All this means is that you call Select on one IEnumerable or IQueryable and get an IEnumerable or IQueryable of something else as the return value.
In this code, taken from Demo4 of the samples, a CityList object, derived from List<City>, is passed to the Where extension method, which passes the filtered Cities to OrderByDescending, whence they go to Take and finally arrive at Select. Select takes each City and creates a new object from it, namely a Tuple of Name and Population. What you get from the Select is thus an IEnumerable of Tuples, specifically IEnumerable<Tuple<string,int>>. That is what's returned by virtue of the return statement!
return Cities
.Where(c => c.IsCapital)
.OrderByDescending(c => c.Population)
.Take(numDesired)
.Select(c => Tuple.Create(c.Name, c.Population));
More...
LINQ can do a whole lot more than this. Besides fooling around with intellisense, another good place to explore is 101 LINQ Samples, on MSDN.
You might also want to continue to Part 6 of this series, on Expression Parameters.
Have fun!