What is LINQ in C# with example?

27 Nov.,2023

 

In this article, we are going to learn about LINQ (Language Integrated Query) in C#. We are going to see why we should use LINQ in our codebase, and different ways to implement and execute LINQ queries. Furthermore, we will explore some of the frequently used LINQ queries.

To download the source code for this article, you can visit our GitHub repository

Let’s dive in.

What is LINQ?

LINQ is a uniform query language, introduced with .NET 3.5 that we can use to retrieve data from different data sources. These data sources include the collection of objects, relational databases, ADO.NET datasets, XML files, etc.

Different Steps of a LINQ Query Operation

Let’s explore the three distinct steps of a LINQ query operation:

  • Obtain the data source
  • Create the query
  • Execute the query

Obtain the Data Source

A valid LINQ data source must support the IEnumerable<T> interface or an interface that inherits from it.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!

So, let’s define a simple data source:

var studentIds = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

The studentIds is an array and it supports the IEnumerable<T> interface. 

Types that support IEnumerable<T> or a derived interface (IQueryable<T>) are called queryable types. A queryable type can directly be applied as a LINQ data source. However, if the data source is not represented in memory as a queryable type, we have to use LINQ providers to load it to a queryable form.

Create the Query

A query specifies what information we want to retrieve from the data source. 

To create a query, we have to import LINQ into our code:

using System.Linq;

Let’s now define the query:

var studentsWithEvenIds =
    from studentId in studentIds
    where (studentId % 2) == 0
    select studentId;

Here, we are returning the IEnumerable<int> collection named studentsWithEvenIds . It holds all the even-numbered student ids.

The query expression has three clauses. The from, where and select. The from clause describes the data source. The where clause applies filters and the select clause produces the result of the query by shaping the data.

get paid? >> JOIN US! <<

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and

We have to pay attention to one important fact – we are not executing the query yet.

Execute the Query

There are two ways to execute a LINQ query:

  • Deferred Execution
  • Immediate Execution

Deferred Execution

We differ the actual execution of our previous query until we iterate over it using a foreach statement. This concept is called deferred execution or lazy loading:

foreach (int studentId in studentsWithEvenIds)
{
    Console.Write("Student Id {0} which is even.", studentId);
}

Immediate Execution

Immediate execution is completely the opposite of deferred execution. Here, we execute our query and get the result immediately. Aggregate functions such as Count, Average, Min, Max, Sum, and element operators such as First, Last, SingleToList, ToArray, ToDictionary are some examples.

We are going to see these functions in action in the rest of the article.

Basic Ways to Write LINQ Queries

There are two basic ways to write LINQ queries:

  • Query Syntax
  • Method Syntax

Query Syntax

To start with our example, we are going to define a method that returns our data source:

static IQueryable<Student> GetStudentsFromDb()
{
    return new[] {  
        new Student() { StudentID = 1, StudentName = "John Nigel", Mark = 73, City ="NYC"} ,
        new Student() { StudentID = 2, StudentName = "Alex Roma",  Mark = 51 , City ="CA"} ,
        new Student() { StudentID = 3, StudentName = "Noha Shamil",  Mark = 88 , City ="CA"} ,
        new Student() { StudentID = 4, StudentName = "James Palatte" , Mark = 60, City ="NYC"} ,
        new Student() { StudentID = 5, StudentName = "Ron Jenova" , Mark = 85 , City ="NYC"} 
    }.AsQueryable();
}

We are going to use a LINQ query syntax to find all the students with Mark higher than 80:

get paid? >> JOIN US! <<

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and

var studentList = GetStudentsFromDb();
var highPerformingStudents = from student in studentList
    where student.Mark > 80
    select student;

The query syntax starts with a from clause. Thereafter, we can use any standard query operator to join, group, or filter the result. In this example, we use where as the standard query operator. The query syntax ends with either a select or a groupBy clause.

Method Syntax

Method syntax use extension methods provided in the Enumerable and Queryable classes.

To see that syntax in action, let’s create another query:

var highPerformingStudents = studentList.Where(s => s.Mark > 80);

In this example, we are using the Where() extension method and provide a lambda expression s => s.Mark > 80 as an argument.

Lambda Expressions With LINQ

In LINQ, we use lambda expressions in a convenient way to define anonymous functions. It is possible to pass a lambda expression as a variable or as a parameter to a method call. However, in many LINQ methods, lambda expressions are used as a parameter. As a result, it makes the syntax short, and precise. Its scope is limited to where it is used as an expression. Therefore, we are not able to reuse it afterward.

To see Lambda expression in play, let’s create a query:

var firstStudent = studentList.Select(x => x.StudentName);

get paid? >> JOIN US! <<

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and

The expression x => x.StudentName is a lambda expression.  x here is an input parameter to the anonymous function representing each object inside the collection.

Frequently Used LINQ Methods 

Since we’ve already seen the Where method in action, let’s take a look at the other top LINQ methods we use in our everyday C# programming. 

Sorting: OrderBy, OrderByDecending

We can use the OrderBy() method to sort a collection in ascending order based on the selected property:

var selectStudentsWithOrderById = studentList.OrderBy(x => x.StudentID);

Similar to OrderBy() method, the OrderByDescending() method sorts the collection using the StudentID property in descending order:

var selectStudentsWithOrderByDescendingId = studentList.OrderByDescending(x => x.StudentID);

Projection: Select

We use the Select method to project each element of a sequence into a new form:

var studentsIdentified = studentList.Where(c => c.StudentName == name)
    .Select(stu => new Student {StudentName = stu.StudentName , Mark = stu.Mark});

Here, we filter only the students with the required name and then use the projection Select method to return students with only StudentName and Mark properties populated. This way, we can easily extract only the required information from our objects.

Grouping: GroupBy

We can use the GroupBy() method to group elements based on the specified key selector function. In this example, we are using City:

var studentListGroupByCity = studentList.GroupBy(x => x.City);

One thing to mention. All the previous methods (Where, OrderBy, OrderByDescending, Select, GroupBy) return collection as a result. So, in order to use all the data inside the collection, we have to iterate over it.

All, Any, Contains

We can use All() to determine whether all elements of a sequence satisfy a condition:

var hasAllStudentsPassed = studentList.All(x => x.Mark > 50);

Similarly, we can use Any() to determine if any element of a sequence exists or satisfies a condition:

var hasAnyStudentGotDistinction = studentList.Any(x => x.Mark > 86);

The Contains() method determines whether a sequence or a collection contains a specified element:

get paid? >> JOIN US! <<

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and

var studentContainsId = studentList.Contains(new Student { StudentName = "Noha Shamil"}, new StudentNameComparer());

Partitioning: Skip, Take

Skip() will bypass a specified number of elements in a sequence and return the remaining elements:

var skipStuentsUptoIndexTwo = studentList.Skip(2);

Take() will return a specified number of elements from the first element in a sequence:

var takeStudentsUptoIndexTwo = studentList.Take(2);

Aggregation: Count, Max, Min, Sum, Average

Applying the Sum() method on the property Mark will give the summation of all marks:

var sumOfMarks = studentList.Sum(x => x.Mark);

We can use the Count() method to return the number of students with a score higher than 65: 

get paid? >> JOIN US! <<

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and

var countOfStudents = studentList.Count(x => x.Mark > 65);

Max() will display the highest Mark scored by a student from the collection:

var maxMarks = studentList.Max(x => x.Mark);

Min() will display the lowest marks scored by a student from the collection:

var minMarks = studentList.Min(x => x.Mark);

We can use Average() to compute the average of a sequence of numerical values:

var avgMarks = studentList.Average(x => x.Mark);

Elements: First, FirstOrDefault, Single, SingleOrDefault

First() returns the first element in the list that satisfies the predicate function. However, if the input sequence is null it throws the ArgumentNullException and if there’s no element for a condition it throws InvalidOperationException:

get paid? >> JOIN US! <<

Wanna join Code Maze Team, help us produce more awesome .NET/C# content and

var firstStudent = studentList.First(x => x.StudentID % 2 == 0);

FirstOrDefault() works similarly to the First() method for positive use cases. If there’s no element found it will return null for reference types and a default value for the value types:

var firstOrDefaultStudent = studentList.FirstOrDefault(x => x.StudentID == 1);

Single() method returns only one element in the collection after satisfying the condition. It also throws the same exceptions as the First() method if the source or predicate is null, or if more than one element satisfies the condition of the predicate:

var singleStudent = studentList.Single(x => x.StudentID == 1);

SingleOrDefault() method works similar to Single() when we find the required element. But if we can’t find an element that meets our condition, the method will return null for reference type or the default value for value types:

var singleOrDefaultStudent = studentList.SingleOrDefault(x => x.StudentID == 1);

Advantages and Disadvantages of Using LINQ

Let’s check some advantages of using LINQ:

  • Improves code readability
  • Provides compile-time object type-checking
  • Provides IntelliSense support for generic collection
  • LINQ queries can be reused
  • Provides in-built methods to write less code and expedite development
  • Provides common query syntax for various data sources

There are also disadvantages of using LINQ:

  • Difficult to write complex queries as SQL
  • Performance degradation if queries are not written accurately
  • Require to recompile, and redeploy every time a change is made to the query
  • Doesn’t take full advantage of SQL features such as cached execution plan for stored procedure

Conclusion

In this article, we’ve learned about LINQ in C#, the three parts of query operations, different ways to implement LINQ queries, and how to use LINQ queries in our codebase. So, this would be enough to get you started with using LINQ in your projects.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!

Write LINQ queries in C#

In this article

Most queries in the introductory Language Integrated Query (LINQ) documentation are written by using the LINQ declarative query syntax. However, the query syntax must be translated into method calls for the .NET common language runtime (CLR) when the code is compiled. These method calls invoke the standard query operators, which have names such as Where, Select, GroupBy, Join, Max, and Average. You can call them directly by using method syntax instead of query syntax.

Query syntax and method syntax are semantically identical, but many people find query syntax simpler and easier to read. Some queries must be expressed as method calls. For example, you must use a method call to express a query that retrieves the number of elements that match a specified condition. You also must use a method call for a query that retrieves the element that has the maximum value in a source sequence. The reference documentation for the standard query operators in the System.Linq namespace generally uses method syntax. Therefore, even when getting started writing LINQ queries, it is useful to be familiar with how to use method syntax in queries and in query expressions themselves.

Standard query operator extension methods

The following example shows a simple query expression and the semantically equivalent query written as a method-based query.

class QueryVMethodSyntax
{
    static void Main()
    {
        int[] numbers = { 5, 10, 8, 3, 6, 12};

        //Query syntax:
        IEnumerable<int> numQuery1 =
            from num in numbers
            where num % 2 == 0
            orderby num
            select num;

        //Method syntax:
        IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

        foreach (int i in numQuery1)
        {
            Console.Write(i + " ");
        }
        Console.WriteLine(System.Environment.NewLine);
        foreach (int i in numQuery2)
        {
            Console.Write(i + " ");
        }

        // Keep the console open in debug mode.
        Console.WriteLine(System.Environment.NewLine);
        Console.WriteLine("Press any key to exit");
        Console.ReadKey();
    }
}
/*
    Output:
    6 8 10 12
    6 8 10 12
 */

The output from the two examples is identical. You can see that the type of the query variable is the same in both forms: IEnumerable<T>.

To understand the method-based query, let's examine it more closely. On the right side of the expression, notice that the where clause is now expressed as an instance method on the numbers object, which as you will recall has a type of IEnumerable<int>. If you are familiar with the generic IEnumerable<T> interface, you know that it does not have a Where method. However, if you invoke the IntelliSense completion list in the Visual Studio IDE, you will see not only a Where method, but many other methods such as Select, SelectMany, Join, and Orderby. These are all the standard query operators.

Although it looks as if IEnumerable<T> has been redefined to include these additional methods, in fact this is not the case. The standard query operators are implemented as a new kind of method called extension methods. Extensions methods "extend" an existing type; they can be called as if they were instance methods on the type. The standard query operators extend IEnumerable<T> and that is why you can write numbers.Where(...).

To get started using LINQ, all that you really have to know about extension methods is how to bring them into scope in your application by using the correct using directives. From your application's point of view, an extension method and a regular instance method are the same.

For more information about extension methods, see Extension Methods. For more information about standard query operators, see Standard Query Operators Overview (C#). Some LINQ providers, such as LINQ to SQL and LINQ to XML, implement their own standard query operators and additional extension methods for other types besides IEnumerable<T>.

Lambda expressions

In the previous example, notice that the conditional expression (num % 2 == 0) is passed as an in-line argument to the Where method: Where(num => num % 2 == 0). This inline expression is called a lambda expression. It is a convenient way to write code that would otherwise have to be written in more cumbersome form as an anonymous method or a generic delegate or an expression tree. In C# => is the lambda operator, which is read as "goes to". The num on the left of the operator is the input variable which corresponds to num in the query expression. The compiler can infer the type of num because it knows that numbers is a generic IEnumerable<T> type. The body of the lambda is just the same as the expression in query syntax or in any other C# expression or statement; it can include method calls and other complex logic. The "return value" is just the expression result.

To get started using LINQ, you do not have to use lambdas extensively. However, certain queries can only be expressed in method syntax and some of those require lambda expressions. After you become more familiar with lambdas, you will find that they are a powerful and flexible tool in your LINQ toolbox. For more information, see Lambda Expressions.

Composability of queries

In the previous code example, note that the OrderBy method is invoked by using the dot operator on the call to Where. Where produces a filtered sequence, and then Orderby operates on that sequence by sorting it. Because queries return an IEnumerable, you compose them in method syntax by chaining the method calls together. This is what the compiler does behind the scenes when you write queries by using query syntax. And because a query variable does not store the results of the query, you can modify it or use it as the basis for a new query at any time, even after it has been executed.

The following examples demonstrate some simple LINQ queries by using each approach listed previously. In general, the rule is to use (1) whenever possible, and use (2) and (3) whenever necessary.

Note

These queries operate on simple in-memory collections; however, the basic syntax is identical to that used in LINQ to Entities and LINQ to XML.

Example - Query syntax

The recommended way to write most queries is to use query syntax to create query expressions. The following example shows three query expressions. The first query expression demonstrates how to filter or restrict results by applying conditions with a where clause. It returns all elements in the source sequence whose values are greater than 7 or less than 3. The second expression demonstrates how to order the returned results. The third expression demonstrates how to group results according to a key. This query returns two groups based on the first letter of the word.

List<int> numbers = new() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

// The query variables can also be implicitly typed by using var

// Query #1.
IEnumerable<int> filteringQuery =
    from num in numbers
    where num < 3 || num > 7
    select num;

// Query #2.
IEnumerable<int> orderingQuery =
    from num in numbers
    where num < 3 || num > 7
    orderby num ascending
    select num;

// Query #3.
string[] groupingQuery = { "carrots", "cabbage", "broccoli", "beans", "barley" };
IEnumerable<IGrouping<char, string>> queryFoodGroups =
    from item in groupingQuery
    group item by item[0];

Note that the type of the queries is IEnumerable<T>. All of these queries could be written using var as shown in the following example:

var query = from num in numbers...

In each previous example, the queries do not actually execute until you iterate over the query variable in a foreach statement or other statement. For more information, see Introduction to LINQ Queries.

Example - Method syntax

Some query operations must be expressed as a method call. The most common such methods are those that return singleton numeric values, such as Sum, Max, Min, Average, and so on. These methods must always be called last in any query because they represent only a single value and cannot serve as the source for an additional query operation. The following example shows a method call in a query expression:

List<int> numbers1 = new() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
List<int> numbers2 = new() { 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 };

// Query #4.
double average = numbers1.Average();

// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);

If the method has Action or Func parameters, these are provided in the form of a lambda expression, as shown in the following example:

// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);

In the previous queries, only Query #4 executes immediately. This is because it returns a single value, and not a generic IEnumerable<T> collection. The method itself has to use foreach in order to compute its value.

Each of the previous queries can be written by using implicit typing with var, as shown in the following example:

// var is used for convenience in these queries
var average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

Example - Mixed query and method syntax

This example shows how to use method syntax on the results of a query clause. Just enclose the query expression in parentheses, and then apply the dot operator and call the method. In the following example, query #7 returns a count of the numbers whose value is between 3 and 7. In general, however, it is better to use a second variable to store the result of the method call. In this manner, the query is less likely to be confused with the results of the query.

// Query #7.

// Using a query expression with method syntax
int numCount1 = (
    from num in numbers1
    where num < 3 || num > 7
    select num
).Count();

// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
    from num in numbers1
    where num < 3 || num > 7
    select num;

int numCount2 = numbersQuery.Count();

Because Query #7 returns a single value and not a collection, the query executes immediately.

The previous query can be written by using implicit typing with var, as follows:

var numCount = (from num in numbers...

It can be written in method syntax as follows:

var numCount = numbers.Where(n => n < 3 || n > 7).Count();

It can be written by using explicit typing, as follows:

int numCount = numbers.Where(n => n < 3 || n > 7).Count();

See also

What is LINQ in C# with example?

Write LINQ queries in C# - C# | Microsoft Learn