Walkthrough: Writing Queries in C# (LINQ)

This walkthrough demonstrates the C# language features that are used to write LINQ query expressions. After completing this walkthrough you will be ready to move on to the samples and documentation for the specific LINQ provider you are interested in, such as LINQ to SQL, LINQ to DataSets, or LINQ to XML.

Prerequisites

This walkthrough requires Visual Studio 2010.

link to video For a video version of this topic, see Video How to: Writing Queries in C# (LINQ).

Create a C# Project

To create a C# project that targets version 3.5 of the .NET Framework

  1. Start Visual Studio.

  2. On the File menu, point to New, and then click Project.

  3. The upper-right corner of the New Project dialog box has three icons. Click the left icon and make sure that .NET Framework Version 3.5 is checked.

  4. Click the Console Application icon under Visual Studio Installed Templates.

  5. Give your application a new name, or accept the default name, and click OK.

  6. Notice that your project has a reference to System.Core.dll and a using directive for the System.Linq namespace.

Create an in-Memory Data Source

The data source for the queries is a simple list of Student objects. Each Student record has a first name, last name, and an array of integers that represents their test scores in the class. Copy this code into your project. Note the following characteristics:

  • The Student class consists of auto-implemented properties.

  • Each student in the list is initialized with an object initializer.

  • The list itself is initialized with a collection initializer.

This whole data structure will be initialized and instantiated without explicit calls to any constructor or explicit member access. For more information about these new features, see Auto-Implemented Properties (C# Programming Guide) and Object and Collection Initializers (C# Programming Guide).

To add the data source

  • Add the Student class and the initialized list of students to the Program class in your project.

    public class Student
    {
        public string First { get; set; }
        public string Last { get; set; }
        public int ID { get; set; }
        public List<int> Scores;
    }
    
    // Create a data source by using a collection initializer.
    static List<Student> students = new List<Student>
    {
       new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores= new List<int> {97, 92, 81, 60}},
       new Student {First="Claire", Last="O’Donnell", ID=112, Scores= new List<int> {75, 84, 91, 39}},
       new Student {First="Sven", Last="Mortensen", ID=113, Scores= new List<int> {88, 94, 65, 91}},
       new Student {First="Cesar", Last="Garcia", ID=114, Scores= new List<int> {97, 89, 85, 82}},
       new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {35, 72, 91, 70}},
       new Student {First="Fadi", Last="Fakhouri", ID=116, Scores= new List<int> {99, 86, 90, 94}},
       new Student {First="Hanying", Last="Feng", ID=117, Scores= new List<int> {93, 92, 80, 87}},
       new Student {First="Hugo", Last="Garcia", ID=118, Scores= new List<int> {92, 90, 83, 78}},
       new Student {First="Lance", Last="Tucker", ID=119, Scores= new List<int> {68, 79, 88, 92}},
       new Student {First="Terry", Last="Adams", ID=120, Scores= new List<int> {99, 82, 81, 79}},
       new Student {First="Eugene", Last="Zabokritski", ID=121, Scores= new List<int> {96, 85, 91, 60}},
       new Student {First="Michael", Last="Tucker", ID=122, Scores= new List<int> {94, 92, 91, 91} }
    };
    

To add a new Student to the Students list

  • Add a new Student to the Students list and use a name and test scores of your choice. Try typing all the new student information in order to better learn the syntax for the object initializer.

Create the Query

To create a simple query

  • In the application's Main method, create a simple query that, when it is executed, will produce a list of all students whose score on the first test was greater than 90. Note that because the whole Student object is selected, the type of the query is IEnumerable<Student>. Although the code could also use implicit typing by using the var keyword, explicit typing is used to clearly illustrate results. (For more information about var, see Implicitly Typed Local Variables (C# Programming Guide).)

    Note also that the query's range variable, student, serves as a reference to each Student in the source, providing member access for each object.


// Create the query.
// The first line could also be written as "var studentQuery ="
IEnumerable<Student> studentQuery =
    from student in students
    where student.Scores[0] > 90
    select student;

Execute the Query

To execute the query

  1. Now write the foreach loop that will cause the query to execute. Note the following about the code:

    • Each element in the returned sequence is accessed through the iteration variable in the foreach loop.

    • The type of this variable is Student, and the type of the query variable is compatible, IEnumerable<Student>.

  2. After you have added this code, build and run the application by pressing Ctrl + F5 to see the results in the Console window.

// Execute the query.
// var could be used here also.
foreach (Student student in studentQuery)
{
    Console.WriteLine("{0}, {1}", student.Last, student.First);
}

To add another filter condition

  • You can combine multiple Boolean conditions in the where clause in order to further refine a query. The following code adds a condition so that the query returns those students whose first score was over 90 and whose last score was less than 80. The where clause should resemble the following code.

    where student.Scores[0] > 90 && student.Scores[3] < 80
    

    For more information, see where clause (C# Reference).

Modify the Query

To order the results

  1. It will be easier to scan the results if they are in some kind of order. You can order the returned sequence by any accessible field in the source elements. For example, the following orderby clause orders the results in alphabetical order from A to Z according to the last name of each student. Add the following orderby clause to your query, right after the where statement and before the select statement:

    orderby student.Last ascending
    
  2. Now change the orderby clause so that it orders the results in reverse order according to the score on the first test, from the highest score to the lowest score.

    orderby student.Scores[0] descending
    
  3. Change the WriteLine format string so that you can see the scores:

    Console.WriteLine("{0}, {1} {2}", student.Last, student.First, student.Scores[0]);
    

    For more information, see orderby clause (C# Reference).

To group the results

  1. Grouping is a powerful capability in query expressions. A query with a group clause produces a sequence of groups, and each group itself contains a Key and a sequence that consists of all the members of that group. The following new query groups the students by using the first letter of their last name as the key.

    // studentQuery2 is an IEnumerable<IGrouping<char, Student>>
    var studentQuery2 =
        from student in students
        group student by student.Last[0];
    
  2. Note that the type of the query has now changed. It now produces a sequence of groups that have a char type as a key, and a sequence of Student objects. Because the type of the query has changed, the following code changes the foreach execution loop also:

    // studentGroup is a IGrouping<char, Student>
    foreach (var studentGroup in studentQuery2)
    {
        Console.WriteLine(studentGroup.Key);
        foreach (Student student in studentGroup)
        {
            Console.WriteLine("   {0}, {1}",
                      student.Last, student.First);
        }
    }
    
  3. Press Ctrl + F5 to run the application and view the results in the Console window.

    For more information, see group clause (C# Reference).

To make the variables implicitly typed

  • Explicitly coding IEnumerables of IGroupings can quickly become tedious. You can write the same query and foreach loop much more conveniently by using var. The var keyword does not change the types of your objects; it just instructs the compiler to infer the types. Change the type of studentQuery and the iteration variable group to var and rerun the query. Note that in the inner foreach loop, the iteration variable is still typed as Student, and the query works just as before. Change the s iteration variable to var and run the query again. You see that you get exactly the same results.

    var studentQuery3 =
        from student in students
        group student by student.Last[0];
    
    foreach (var groupOfStudents in studentQuery3)
    {
        Console.WriteLine(groupOfStudents.Key);
        foreach (var student in groupOfStudents)
        {
             Console.WriteLine("   {0}, {1}",
                 student.Last, student.First);
        }
    }
    

    For more information about var, see Implicitly Typed Local Variables (C# Programming Guide).

To order the groups by their key value

  • When you run the previous query, you notice that the groups are not in alphabetical order. To change this, you must provide an orderby clause after the group clause. But to use an orderby clause, you first need an identifier that serves as a reference to the groups created by the group clause. You provide the identifier by using the into keyword, as follows:

                var studentQuery4 =
                    from student in students
                    group student by student.Last[0] into studentGroup
                    orderby studentGroup.Key
                    select studentGroup;
    
                foreach (var groupOfStudents in studentQuery4)
                {
                    Console.WriteLine(groupOfStudents.Key);
                    foreach (var student in groupOfStudents)
                    {
                        Console.WriteLine("   {0}, {1}",
                            student.Last, student.First);
                    }
                }
    
    

    When you run this query, you will see the groups are now sorted in alphabetical order.

To introduce an identifier by using let

  • You can use the let keyword to introduce an identifier for any expression result in the query expression. This identifier can be a convenience, as in the following example, or it can enhance performance by storing the results of an expression so that it does not have to be calculated multiple times.

    // studentQuery5 is an IEnumerable<string>
    // This query returns those students whose
    // first test score was higher than their
    // average score.
    var studentQuery5 =
        from student in students
        let totalScore = student.Scores[0] + student.Scores[1] +
            student.Scores[2] + student.Scores[3]
        where totalScore / 4 < student.Scores[0]
        select student.Last + " " + student.First;
    
    foreach (string s in studentQuery5)
    {
        Console.WriteLine(s);
    }
    

    For more information, see let clause (C# Reference).

To use method syntax in a query expression

  • As described in LINQ Query Syntax versus Method Syntax (C#), some query operations can only be expressed by using method syntax. The following code calculates the total score for each Student in the source sequence, and then calls the Average() method on the results of that query to calculate the average score of the class. Note the placement of parentheses around the query expression.

        var studentQuery6 = 
            from student in students
            let totalScore = student.Scores[0] + student.Scores[1] +
                student.Scores[2] + student.Scores[3]
            select totalScore;
    
        double averageScore = studentQuery6.Average();
    Console.WriteLine("Class average score = {0}", averageScore);
    

To transform or project in the select clause

  1. It is very common for a query to produce a sequence whose elements differ from the elements in the source sequences. Delete or comment out your previous query and execution loop, and replace it with the following code. Note that the query returns a sequence of strings (not Students), and this fact is reflected in the foreach loop.

                IEnumerable<string> studentQuery7 =
                    from student in students
                    where student.Last == "Garcia"
                    select student.First;
    
                Console.WriteLine("The Garcias in the class are:");
                foreach (string s in studentQuery7)
                {
                    Console.WriteLine(s);
                }
    
    
  2. Code earlier in this walkthrough indicated that the average class score is approximately 334. To produce a sequence of Students whose total score is greater than the class average, together with their Student ID, you can use an anonymous type in the select statement:

                var studentQuery8 =
                    from student in students
                    let x = student.Scores[0] + student.Scores[1] +
                        student.Scores[2] + student.Scores[3]
                    where x > averageScore
                    select new { id = student.ID, score = x };
    
                foreach (var item in studentQuery8)
                {
                    Console.WriteLine("Student ID: {0}, Score: {1}", item.id, item.score);
                }
    
    

Next Steps

After you are familiar with the basic aspects of working with queries in C#, you are ready to read the documentation and samples for the specific type of LINQ provider you are interested in:

LINQ to SQL

LINQ to DataSet

LINQ to XML

LINQ to Objects

See Also

Tasks

Walkthrough: Writing Queries in Visual Basic

Concepts

LINQ Query Expressions (C# Programming Guide)

Supplementary LINQ Resources

Other Resources

LINQ (Language-Integrated Query)

Getting Started with LINQ in C#