Hooked on LINQ

Hooked on LINQ - Developers' Wiki
for .NET Language Integrated Query

Quick Search

Advanced Search »

LINQ to Objects - 5 Minute Overview

This Page is locked
Modified: 2008/04/24 23:15 by t_magennis - Categorized as: LINQ to Objects, Samples
I want feedback on this article. Please leave your comments in the discussion page (click on the Discuss button on the top-right of this page)

(See also: Main LINQ to Objects Page)


LINQ to Objects allows .NET developers to write “queries” over collections of objects. Out of the box there is a large set of query operators that provide a similar depth of functionality to what we expect from any SQL language working with a relational database, and if what we need isn’t present out-of-the-box, we can add our own.

Traditionally, working with collections of objects meant writing a lot of looping code using for loops or foreach loops to iterate through a list carrying out filtering using if statements, and some action like keeping a running sum of a total property. LINQ frees you from having to write looping code; it allows you to write queries that filter a list or calculate aggregate functions on elements in a collection as a set.

We can write queries against any collection type that implements an interface called IEnumerable (and also a new interface called IQueryable, but more on that later). This is almost any collection type built into the .NET class libraries including simple arrays like string[], or int[], and any List<T> collection we define. Let us look at a few of the simplest examples to understand the basic syntax.

int[] nums = new int[] {0,4,2,6,3,8,3,1};
 
 
var result = from n in nums
             where n < 5
             orderby n
             select n;
 
 
foreach(int i in result)
    Console.WriteLine(i);
 
 
Output:
0
1
2
3
3	
4



An example of calculating the aggregate sum of all the elements would look like –

int[] nums = new int[] {0,4,2,6,3,8,3,1};
 
int result = nums.Sum();
Console.WriteLine(result);
 
Output:
27



LINQ to Objects extends any type that inherits from IEnumerable (which is almost every collection class in .NET, from simple Arrays to List<T>) to support query operations similar to those available in SQL. We can write queries using any of the built-in Standard Query Operators, or add our own operators if we need to. The standard operators cover a wide variety of categories, at present there are over fifty that form the backbone of LINQ. To get an idea of their scope, here is a list of those operators available to us -


Most of the operators should be familiar if you have ever worked with a relational database writing queries in SQL. One important distinction between writing SQL queries and LINQ queries is that the operator order is reversed. If you are used to Select-From-Where-OrderBy, it might take some time to overcome the muscle memory and move to From-Where-OrderBy-Select.

To demonstrate some of LINQ’s query capabilities let us write a few queries over this data.

First NameLast NameD.O.B.PhoneState
BarneyGottshall19-Oct-1945885 983 8858CA
ArmandoValdes9-Dec-1973848 553 8487WA
AdamGauwain3-Oct-1959115 999 1154AK
JefferyDeane16-Dec-1950677 602 6774CA
CollinZeeman10-Feb-1935603 303 6030FL
StewartKagel20-Feb-1950546 607 5462WA
ChanceLard21-Oct-1951278 918 2789WA
BlaineReifsteck18-May-1946715 920 7157TX
MackKamph17-Sep-1977364 202 3644TX
ArielHazelgrove23-May-1922165 737 1656OR
Sample Contact data

List<Contacts> contacts = Contacts.SampleData();
 
var q = from c in contacts
        where c.DateOfBirth.AddYears(35) > DateTime.Now
        orderby c.DateOfBirth descending
        select c.FirstName + " " + c.LastName +
               " b." + c.DateOfBirth.ToString("dd-MMM-yyyy");
 
foreach(string s in q)
    Console.WriteLine(s);
 
Output:
Mack Kamph b.17-Sep-1977
Armando Valdes b.09-Dec-1973



The previous example demonstrates how to get a list of contacts who are less than 35 years of age sorted in descending order by age. This query builds a list of formatted strings as the result, but any type can be returned, even an anonymous type (a type we haven’t explicitly defined that holds just our data, but more on that later). Figure 6 demonstrates grouping, which allows you to create a sub-collection of elements based on a value by using the group by construct.

List<Contacts> contacts = Contacts.SampleData();
 
var q = from c in contacts
        group c by c.State;
 
foreach(var group in q) {
    Console.WriteLine("State: " + group.Key);
        foreach(Contacts c in group)
            Console.WriteLine("  {0) {1}", c.FirstName, c.LastName);
}
 
Output:
State: CA
  Barney Gottshall
  Jeffery Deane
State: WA
  Armando Valdes
  Stewart Kagel
  Chance Lard
State: AK
  Adam Gauwain
State: FL
  Collin Zeeman
State: TX
  Blaine Reifsteck
  Mack Kamph
State: OR
  Ariel Hazelgrove



A key aspect of accessing relational data is the concept of joining. SQL languages have powerful join capabilities to allow queries to be written against normalized data which is a fancy term for not repeating data, by separating data across multiple tables linking by a common value. LINQ allows you to join multiple object collections together using syntax similar to SQL. To demonstrate, in addition to the data shown in Sample Contact data, also consider the following call log data.

NumberDuration (mins)IncomingDateTime
885 983 88582TRUE7-Aug-20068:12
165 737 165615TRUE7-Aug-20069:23
364 202 36441FALSE7-Aug-200610:5
603 303 60302FALSE7-Aug-200610:35
546 607 54624TRUE7-Aug-200611:15
885 983 885815FALSE7-Aug-200613:12
885 983 88583TRUE7-Aug-200613:47
546 607 54621FALSE7-Aug-200620:34
546 607 54623FALSE8-Aug-200610:10
603 303 603023FALSE8-Aug-200610:40
848 553 84873FALSE8-Aug-200614:0
848 553 84877TRUE8-Aug-200614:37
278 918 27896TRUE8-Aug-200615:23
364 202 364420TRUE8-Aug-200617:12
Sample Call Log data

To join the call log data and retrieve the contact name that matches the phone number we would use the following query.

 
    List<Contacts> contacts = Contacts.SampleData();
    List<CallLog> callLog = CallLog.SampleData();
 
 
    var q = from call in callLog
            join contact in contacts on call.Number equals contact.Phone
            select new {contact.FirstName, contact.LastName, 
                        call.When, call.Duration};
                    
     foreach(var call in q)
         Console.WriteLine({0}{1) {2) ({3}min)”,
                           call.When.ToString("ddMMM HH:m"),
                           call.FirstName, call.LastName, call.Duration);
 
Output:
07Aug 08:12 - Barney Gottshall (2min)
07Aug 09:23 - Ariel Hazelgrove (15min)
07Aug 10:5 - Mack Kamph (1min)
07Aug 10:35 - Collin Zeeman (2min)
07Aug 11:15 - Stewart Kagel (4min)
07Aug 13:12 - Barney Gottshall (15min)
07Aug 13:47 - Barney Gottshall (3min)
07Aug 20:34 - Stewart Kagel (1min)
08Aug 10:10 - Stewart Kagel (3min)
08Aug 10:40 - Collin Zeeman (23min)
08Aug 14:0 - Armando Valdes (3min)
08Aug 14:37 - Armando Valdes (7min)
08Aug 15:23 - Chance Lard (6min)
08Aug 17:12 - Mack Kamph (20min)

This query joins call records with contact data via the phone number.

Going one step further and summarizing data from multiple collections by combining filtering, grouping, joining and aggregate functions all in one query expression, we can demonstrate the power of Query Expressions on in-memory data. The following example show incoming call records from each contact and the aggregate call statistics.

    List<Contacts> contacts = Contacts.SampleData();
    List<CallLog> callLog = CallLog.SampleData();
 
    var q = from call in callLog
            where call.Incoming == true
            group call by call.Number into g
            join contact in contacts on g.Key equals contact.Phone
            orderby contact.FirstName, contact.LastName
            select new { contact.FirstName, contact.LastName, 
                         Count = g.Count(), 
                         Avg   = g.Average( c => c.Duration ), 
                         Total = g.Sum( c => c.Duration )};
            
    foreach(var call in q)
        Console.WriteLine("{0} {1} - Calls:{2}, Time:{3}mins, Avg:{4}mins", 
            call.FirstName, call.LastName, 
            call.Count, call.Total, Math.Round(call.Avg, 2));
 
Output:
Ariel Hazelgrove - Calls:1, Time:15mins, Avg:15mins
Armando Valdes - Calls:1, Time:7mins, Avg:7mins
Barney Gottshall - Calls:2, Time:5mins, Avg:2.5mins
Chance Lard - Calls:1, Time:6mins, Avg:6mins
Mack Kamph - Calls:1, Time:20mins, Avg:20mins
Stewart Kagel - Calls:1, Time:4mins, Avg:4mins

This example shows filtering, ordering, grouping, joining and selection using aggregate values.

(See also: Main LINQ to Objects Page)


LINQ to Object - 5 Minute Overview is Copyright © Troy Magennis.

If you would like to comment on this page, click on the Discuss button located on the top-right of each page. Feel free to edit any mistakes or ommissions you find. If you have an objection or find in-appropriate content then contact the administrator. This website is not affiliated with Microsoft®, all content and opinions are those of the specific author and some advice, solutions and article may contain un-intentional errors - please use care. Powered by ScrewTurn Wiki version 2.0.33. Some of the icons created by FamFamFam.