LINQ In C#: Learn The Fundamentals

by Kenji Elzerman
LINQ Fundamentals - Kens Learning Curve

LINQ in C# has been around since 2007 and is growing in popularity. It’s a great way to quickly iterate and filter lists, whether it’s a list of data values or objects. If you use Entity Framework chances are you are using LINQ in C#. But what is LINQ and what are the fundamentals of this framework?

Prefer to see the video?

What is LINQ in C#?

LINQ in C# stands for Language Integrated Query and is a replacement for the good old queries. We used to write queries in strings, making them typo-sensitive, not easy to change, not able to use IntelliSense, and they are one big string. LINQ solves these ‘problems’.

In essence, LINQ in C# consists of extensions that you can use on different types. But we use LINQ in C# on collection types like IEnumerable, List<>, List, array, IQueryable, Dictionary<>, etc.

It is possible to use LINQ for different data sources:

  • objects.
  • ADO.
  • XML.
  • Entity Framework.
  • SQL Database.
  •  Other, as long as you use IQueryable or IEnumerable.

LINQ in C# comes in two variants: Lambda expressions and query expressions. The query expressions look the most like SQL statements. Performance wise there is no difference between them. It’s more about how you want to use them and what your preference is. 

Lambda expressions vs query expressions

As I said; there is no difference between lambda and query expressions if you look at the performance. But there is a big difference in syntax. Look at the code below:

internal class LinqDemo
{
    private List<string> movieTitles = new() { "The Matrix", "Shrek", "Inception", "Ice Age", "John Wick" };
    public List<string> Search(string query)
    {
        var resultLambda = movieTitles.Where(x => x.Contains(query, StringComparison.CurrentCultureIgnoreCase));
        var resultQuery = from titles in movieTitles
                            where titles.Contains(query)
                            select titles;
        return null;
    }
}

Let’s take a look at lines 7 and 9 to 11. The first one (line 7) executes a lambda expression. Lines 9 to 11 execute a query expression. They both return the same result, namely an IEnumerable. The only difference is the syntax. I like the lambda more. Purely because it’s somehow easier for me to write. I only use the query expressions when I need to join lists.

So, lambda:

  • Creates an anonymous function.
  • The characteristic is the lambda operator.
  • Has methods that execute the lambda and reserve memory.

Query:

  • Looks like TSQL.
  • Recognizable.
  • Is transcribed to Lambda.
  • Will only be executed when read or used.

There is no answer to which one is better because it’s a personal taste. For me, lambda works better. So the next examples will be given with lambda.

Lambda explained

Let’s look at the previous code:

var resultLambda = movieTitles.Where(x => x.Contains(query, StringComparison.CurrentCultureIgnoreCase));

What is happening here? Well, in short: resultLamba contains all the movies that contain a value, which is stored in the query. Let’s say the query contains ‘a’, then only The Matrix and Ice Age will be placed in resultQuery. The Where method is a method that only returns those values from a list that match the input sequence (x.Contains(….), where x represents an item from the list).

Another example with the Where:

List<Movie> movieTitles = new() 
{
    new()
    {
        Id = 1,
        Title = "The Matrix",
        HasBeenSeen = true
    },
    new()
    {
        Id = 2,
        Title = "Shrek",
        HasBeenSeen = true
    },
    new()
    {
        Id = 3,
        Title = "Inception",
        HasBeenSeen = true
    },
    new()
    {
        Id = 4,
        Title = "Ice Age",
    },
    new()
    {
        Id = 5,
        Title = "John Wick"
    }
};
var seenMovies = movieTitles.Where(x => x.HasBeenSeen);
foreach (var item in seenMovies)
{
    Console.WriteLine(item.Title);
}
public class Movie
{
    public int Id { get; set; }
    public string Title { get; set; }
    public bool HasBeenSeen { get; set; }
}

I’ve created a list of movies, containing an ID, title, and a boolean HasBeenSeen, which states if I have seen the movie already (true) or not (false).

On line 33 I use the Where method to filter out the movies that have been seen. X represents a movie from the list movieTitles.

In a loop

The Where method is a short version of the For-Each loop. I could rewrite line 33 to not use a lambda expression, but a simple for-each:

List<Movie> seenMovies = new();
foreach (var movie in movieTitles)
{
    if (movie.HasBeenSeen)
        movieTitles.Add(movie);
}
foreach (var item in seenMovies)
{
    Console.WriteLine(item.Title);
}

Lines 1 to 6 do the same as the lambda expression from the previous code example on line 33.

As you can see, LINQ in C# does not only work great on lists, but it also makes the code smaller.

Most used LINQ in C# statements

Here is a small list of the most used LINQ statements.

Where

Filters a list by a predicate. See the examples above.

movieTitles.Where(x => x.HasBeenSeen);

Select

Selects the fields or properties you want to return and place them in a new (anonymous) object. Or return a list of a single property.

var seenMovies = movieTitles.Select(x => new
{
    x.Title,
    NeedToSee = x.HasBeenSeen,
});
var seenMovies = movieTitles.Select(x => x.Title);

Single(OrDefault)

Returns one element, filtered by a predicate. If no element is found an exception will be thrown. To avoid the exception, use the SingleOrDefault. If the element is not found, the return value will be NULL. When there is more than one element an exception will be thrown, even when using SingleOrDefault.

If a single element is an int or a Boolean, the default value will not be NULL. It will be 0 or false since integers and Booleans can’t be NULL.

var found = movieTitles.SingleOrDefault(x => x.HasBeenSeen); // Throws exception
var found = movieTitles.SingleOrDefault(x => x.Title.Contains("z")); // returns NULL
var found = movieTitles.SingleOrDefault(x => x.Equals("The Matrix")); // returns the movie The Matrix.

Any

The any is used to check if a certain filter is true or not. For example: you want to check if there are movies that haven’t been seen yet. Instead of using the Where and checking if the number of items is more than 1, you can use the Any method.

if(movieTitles.Any(x=> !x.HasBeenSeen))
{
    Console.WriteLine("There are still movies to be seen.");
}

Note the exclamation mark (!) on line 1. This means the HasBeenSeen has to be false.

OrderBy(Desc)

Orders a list on a specific property. There are two variants:

  • OrderBy
    Orders from A-Z or 0-9
  • OrderByDesc
    Orders from Z-A or 9-0

The OrderBy(Desc) returns a new object of the type IOrderedQueryable. 

var ordered = movieTitles.OrderBy(x => x.Title);

First(OrDefault)/Last(OrDefault)

The First statement returns the very first item in the list. If the list is empty it will throw an exception. To avoid this exception you can use the FirstOrDefault, which has the same idea as the SingleOrDefault.

Oppositely, the Last statement returns the very last item in the list. If the list is empty it will throw an exception. To avoid this exception you can use the LastOrDefault, which has the same idea as the SingleOrDefault.

var lastItem = movieTitles.Last();
var firstItem = movieTitles.First();
var firstItemWithPredicate = movieTitles.First(x => x.HasBeenSeen);

The last line, line 3, returns the first item in the list after filtering on the HasBeenSeen, which only renders the movies that have been seen.

Chaining LINQ in C# statements

One of the things I love the most about LINQ in C# is that you can chain the methods. This means you can put all the LINQ in C# statements behind each other and you rarely have to create new variables. An example:

Movie first = movieTitles.OrderBy(x => x.Title).Where(x => x.Id > 3).First();

In the example above I combined multiple LINQ statements in one row.

IEnumerable vs List

Ah, a question I had so many times when having an interview for a new job:

“Can you tell me the differences between IEnumerable and a List?”

Usually, I just said that one is an interface and the other is an object… I am not wrong. But it’s not what they wanted to hear. 

If you work or starting to work with LINQ in C# you should know the difference a bit more. Especially the reason why LINQ in C# is returning IEnumerable values.

  • IEnumerable hasn’t been executed yet.
  • List() executes the query on the IEnumerable.
  • You can keep filtering on an IEnumerable without executing the filter.
  • IEnumerable provides a deferred execution
    The immediate return value is an object that stores all the information that is required to perform the action. The query represented by this method is not executed until the object is enumerated either by calling its GetEnumerator method directly or by using foreach in Visual C# or For Each in Visual Basic.

So, an IEnumerable stores the information that will be executed later. But … why? Let’s take a look at a database.

LINQ in C# and Entity Framework

If you are using Entity Framework chances are you get data from the entities, filter them, or do other stuff via LINQ in C#. Imagine having a database with a table of movies. That table contains 100.000 movies. Say that only 10 movies have “The” in the title… Do you get 100.000 movies from the database and then filter them? It would be better to filter in an SQL query like this:

SELECT * FROM Movies WHERE Title like '%the%'

But we are using Entity Framework. Filtering on “the” will be done with the Where-statement and looks like this:

DataContext dbContext = new();
var seenMovies = dbContext.Movies
    .Where(x => x.Title.Contains("the"));

Let’s start the application and debug the “seenMovies”. Note that the seenMovies variable is still an IEnumerable.

Results from Entity Framework - Learn the fundamentals of LINQ in C# - Kens Learning Curve
Results from Entity Framework.

When you look at this you see that the type of seenMovies is EntityQueryable, which inherits IEnumerable. The property “Results View” has some sort of warning:

Expanding the Results View will enumerate the IEnumerable.

This means that if you expand it, the query of the IEnumerable will be executed on the (SQL) server. 

Execute the query

Now, let’s execute the same code, but with one small change:

DataContext dbContext = new();
var seenMovies = dbContext.Movies
    .Where(x => x.Title.Contains("the"))
    .ToList();
Results from Entity Framework with ToList - Learn the fundamentals of LINQ in C# - Kens Learning Curve
Results from Entity Framework with ToList()

The result is different: You have a list of movies now with one movie (which is expected). The query of the IEnumerable has been executed and results in a List<Movie>.

Mapping the result

So, what’s the big advantage of executing the query at a later stage? Well, you can implement multiple LINQ in C# statements before executing. Let’s say we create a method with an extended filter. The filter looks like this:

public class Filter
{
    public string query { get; set; }
    public bool? hasBeenSeen { get; set; }
    public string OrderBy { get;set; }
}

All properties are optional. If the query is empty, don’t filter on the query. However, when the property hasBeenSeen is NULL, show seen and not seen movies. And now the implementation of the method:

public List<Movie> Filter(Filter filter)
{
    IEnumerable<Movie> movies = dbContext.Movies;
    if (!string.IsNullOrEmpty(filter.query))
        movies = movies.Where(x => x.Title.Contains(filter.query, StringComparison.CurrentCultureIgnoreCase));
    if (filter.hasBeenSeen.HasValue)
        movies = movies.Where(x => x.HasBeenSeen == filter.hasBeenSeen.Value);
    return movies.ToList();
}

On line 2 I get the movies from the context. Well, not really. I make a link (not LINQ) to the movies. The code on line 5 checks if the query is filled or not. If it is, I add the Where on line 6.  On line 8 I check if the hasBeenSeen has a value. If so, I add another Where on line 9.

Now, I did 3 things with LINQ on an entity, but the data was not retrieved from the actual database. That only happens on line 11.

If you use a profiler on the database, put a breakpoint on line 11 (so the IEnumerable is not executed yet), and start the application you will notice that nothing is executed on the database, until you pass line 11. 

Conclusion

Well, that covers most of the LINQ in C#. I hope you notice you can use it fairly easily. There is plenty of documentation online that can help you achieve your LINQ challenges. If you want more information about the topic check out Microsoft Learn. 

Related Articles

Table Of Contents
Kens Learning Curve