Dela via


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 features that are introduced in Visual Studio 2008.

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 project

  1. Start Visual Studio.

  2. On the menu bar, choose File, New, Project.

    The New Project dialog box opens.

  3. Expand Installed, expand Templates, expand Visual C#, and then choose Console Application.

  4. In the Name text box, enter a different name or accept the default name, and then choose the OK button.

    The new project appears in Solution Explorer.

  5. 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);
}

// Output: 
// Omelchenko, Svetlana 
// Garcia, Cesar 
// Fakhouri, Fadi 
// Feng, Hanying 
// Garcia, Hugo 
// Adams, Terry 
// Zabokritski, Eugene 
// Tucker, Michael

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);
        }
    }
    
    // Output: 
    // O 
    //   Omelchenko, Svetlana 
    //   O'Donnell, Claire 
    // M 
    //   Mortensen, Sven 
    // G 
    //   Garcia, Cesar 
    //   Garcia, Debra 
    //   Garcia, Hugo 
    // F 
    //   Fakhouri, Fadi 
    //   Feng, Hanying 
    // T 
    //   Tucker, Lance 
    //   Tucker, Michael 
    // A 
    //   Adams, Terry 
    // Z 
    //   Zabokritski, Eugene
    
  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);
        }
    }
    
    // Output: 
    // O 
    //   Omelchenko, Svetlana 
    //   O'Donnell, Claire 
    // M 
    //   Mortensen, Sven 
    // G 
    //   Garcia, Cesar 
    //   Garcia, Debra 
    //   Garcia, Hugo 
    // F 
    //   Fakhouri, Fadi 
    //   Feng, Hanying 
    // T 
    //   Tucker, Lance 
    //   Tucker, Michael 
    // A 
    //   Adams, Terry 
    // Z 
    //   Zabokritski, Eugene
    

    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);
        }
    }
    
    // Output: 
    //A 
    //   Adams, Terry 
    //F 
    //   Fakhouri, Fadi 
    //   Feng, Hanying 
    //G 
    //   Garcia, Cesar 
    //   Garcia, Debra 
    //   Garcia, Hugo 
    //M 
    //   Mortensen, Sven 
    //O 
    //   Omelchenko, Svetlana 
    //   O'Donnell, Claire 
    //T 
    //   Tucker, Lance 
    //   Tucker, Michael 
    //Z 
    //   Zabokritski, Eugene
    

    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);
    }
    
    // Output: 
    // Omelchenko Svetlana 
    // O'Donnell Claire 
    // Mortensen Sven 
    // Garcia Cesar 
    // Fakhouri Fadi 
    // Feng Hanying 
    // Garcia Hugo 
    // Adams Terry 
    // Zabokritski Eugene 
    // Tucker Michael
    

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

To use method syntax in a query expression

  • As described in Query Syntax and Method Syntax in LINQ (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);
    
    // Output: 
    // Class average score = 334.166666666667
    

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);
    }
    
    // Output: 
    // The Garcias in the class are: 
    // Cesar 
    // Debra 
    // Hugo
    
  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);
    }
    
    // Output: 
    // Student ID: 113, Score: 338 
    // Student ID: 114, Score: 353 
    // Student ID: 116, Score: 369 
    // Student ID: 117, Score: 352 
    // Student ID: 118, Score: 343 
    // Student ID: 120, Score: 341 
    // Student ID: 122, Score: 368
    

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 SQL]

LINQ to DataSet

LINQ to XML [from BPUEDev11]

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#