Where is LINQ in C#?

27 Nov.,2023

 

Language Integrated Query (LINQ)

In this article

Language-Integrated Query (LINQ) is the name for a set of technologies based on the integration of query capabilities directly into the C# language. Traditionally, queries against data are expressed as simple strings without type checking at compile time or IntelliSense support. Furthermore, you have to learn a different query language for each type of data source: SQL databases, XML documents, various Web services, and so on. With LINQ, a query is a first-class language construct, just like classes, methods, events.

For a developer who writes queries, the most visible "language-integrated" part of LINQ is the query expression. Query expressions are written in a declarative query syntax. By using query syntax, you can perform filtering, ordering, and grouping operations on data sources with a minimum of code. You use the same basic query expression patterns to query and transform data in SQL databases, ADO.NET Datasets, XML documents and streams, and .NET collections.

The following example shows the complete query operation. The complete operation includes creating a data source, defining the query expression, and executing the query in a foreach statement.

// Specify the data source.
int[] scores = { 97, 92, 81, 60 };

// Define the query expression.
IEnumerable<int> scoreQuery =
    from score in scores
    where score > 80
    select score;

// Execute the query.
foreach (int i in scoreQuery)
{
    Console.Write(i + " ");
}

// Output: 97 92 81

Query expression overview

  • Query expressions can be used to query and to transform data from any LINQ-enabled data source. For example, a single query can retrieve data from a SQL database, and produce an XML stream as output.

  • Query expressions are easy to grasp because they use many familiar C# language constructs.

  • The variables in a query expression are all strongly typed, although in many cases you do not have to provide the type explicitly because the compiler can infer it. For more information, see Type relationships in LINQ query operations.

  • A query is not executed until you iterate over the query variable, for example, in a foreach statement. For more information, see Introduction to LINQ queries.

  • At compile time, query expressions are converted to Standard Query Operator method calls according to the rules set forth in the C# specification. Any query that can be expressed by using query syntax can also be expressed by using method syntax. However, in most cases query syntax is more readable and concise. For more information, see C# language specification and Standard query operators overview.

  • As a rule when you write LINQ queries, we recommend that you use query syntax whenever possible and method syntax whenever necessary. There is no semantic or performance difference between the two different forms. Query expressions are often more readable than equivalent expressions written in method syntax.

  • Some query operations, such as Count or Max, have no equivalent query expression clause and must therefore be expressed as a method call. Method syntax can be combined with query syntax in various ways. For more information, see Query syntax and method syntax in LINQ.

  • Query expressions can be compiled to expression trees or to delegates, depending on the type that the query is applied to. IEnumerable<T> queries are compiled to delegates. IQueryable and IQueryable<T> queries are compiled to expression trees. For more information, see Expression trees.

How to enable LINQ querying of your data source

In-memory data

There are two ways you can enable LINQ querying of in-memory data. If the data is of a type that implements IEnumerable<T>, you can query the data by using LINQ to Objects. If it does not make sense to enable enumeration of your type by implementing the IEnumerable<T> interface, you can define LINQ standard query operator methods in that type or create LINQ standard query operator methods that extend the type. Custom implementations of the standard query operators should use deferred execution to return the results.

Remote data

The best option for enabling LINQ querying of a remote data source is to implement the IQueryable<T> interface. However, this differs from extending a provider such as LINQ to SQL for a data source.

IQueryable LINQ providers

LINQ providers that implement IQueryable<T> can vary widely in their complexity.

A less complex IQueryable provider might interface with a single method of a Web service. This type of provider is very specific because it expects specific information in the queries that it handles. It has a closed type system, perhaps exposing a single result type. Most of the execution of the query occurs locally, for example by using the Enumerable implementations of the standard query operators. A less complex provider might examine only one method call expression in the expression tree that represents the query, and let the remaining logic of the query be handled elsewhere.

An IQueryable provider of medium complexity might target a data source that has a partially expressive query language. If it targets a Web service, it might interface with more than one method of the Web service and select the method to call based on the question that the query poses. A provider of medium complexity would have a richer type system than a simple provider, but it would still be a fixed type system. For example, the provider might expose types that have one-to-many relationships that can be traversed, but it would not provide mapping technology for user-defined types.

A complex IQueryable provider, such as the LINQ to SQL provider, might translate complete LINQ queries to an expressive query language, such as SQL. A complex provider is more general than a less complex provider, because it can handle a wider variety of questions in the query. It also has an open type system and therefore must contain extensive infrastructure to map user-defined types. Developing a complex provider requires a significant amount of effort.

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!

Where is LINQ in C#?

LINQ Basic Concepts in C# - Code Maze C# LINQ