DEV Community

Cover image for Getting Started with LINQ (1/3)
Mirza Leka
Mirza Leka

Posted on • Updated on

Getting Started with LINQ (1/3)

Language Integrated Query or LINQ is a C# feature that allows you to query different data sources, such as:

  • In memory collections (arrays, lists, dictionaries)
  • SQL collections
  • XML collections

using a unified language syntax.

There are two ways to use LINQ:

  • Query syntax consist of a set of query keywords defined into the .NET Framework that resemble SQL-Like commands.
  • Method syntax consist of operator functions (extension methods) chained together using a declarative programming paradigm.

For this tutorial we'll use LINQ Extension method syntax.

Where can we use LINQ?

Language Integrated Query syntax works with any type of collection in C# that implements the IEnumerable interface, such as arrays, lists, dictionaries, etc. Simply put, whenever you see a type IEnumerable or type that implements the IEnumerable, you can use LINQ.

Setup

To speed things up, I've already created a collection of students in the JSON file. The Gist I wrote will explain how to import the JSON data into a C# collection.

Each student is a class containing properties:

public class Student
{
    public int ID { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Country { get; set; }
}
Enter fullscreen mode Exit fullscreen mode
var student = new LINQTutorial();
List<Student> students = student.GetStudents();
Enter fullscreen mode Exit fullscreen mode

The method GetStudents() on the student instance retrieves a collection of students populated using the JSON file. More in the Gist.

Now let's make use of various LINQ Operators to query the students data.

FILTERS

First & Last

The First() operator returns the first record in the collection:

var firstStudent = students.First();

Console.WriteLine($"Name: {firstStudent.Name}, Age: {firstStudent.Age}, Country: {firstStudent.Country}");
// Name: Mirza, Age: 18, Country: Bosnia
Enter fullscreen mode Exit fullscreen mode

The opposite of First() is the Last() operator that returns the last record:

var lastStudent = students.Last();

Console.WriteLine($"Name: {lastStudent.Name}, Age: {lastStudent.Age}, Country: {lastStudent.Country}");
// Name: Amy, Age: 21, Country: USA
Enter fullscreen mode Exit fullscreen mode

This is one way of using First() and Last() operators. The LINQ operators have an overload method that accepts a predicate and return the record/s that match the condition inside.

var firstStudent = students.First(x => x.Age == 19);

Console.WriteLine($"Name: {firstStudent.Name}, Age: {firstStudent.Age}, Country: {firstStudent.Country}");
// Name: Alan, Age: 19, Country: UK

var lastStudent = students.Last(x => x.Age == 20);

Console.WriteLine($"Name: {lastStudent.Name}, Age: {lastStudent.Age}, Country: {lastStudent.Country}");
// Name: Raj, Age: 20, Country: India
Enter fullscreen mode Exit fullscreen mode

First/Last Or Default

What would happen if we'd query for first student with the age of 100?

var firstStudent = students.First(x => x.Age == 100);
Enter fullscreen mode Exit fullscreen mode

Because there is no record of a student with age 100, the C# program execution will stop and throw an exception:

System.InvalidOperationException: Sequence contains no matching element
Enter fullscreen mode Exit fullscreen mode

To prevent this we can use the FirstOrDefault() operator that works just the same as the First(), with an exception that it returns the default value (null for strings and objects, 0 for numbers) if no record meets the condition.

var firstStudent = students.FirstOrDefault(x => x.Age == 19); 
// {...} 

var nonExistingStudent = students.FirstOrDefault(x => x.Age == 100);
// null

var nonExistingStudentName = students
    .Select(s => s.Name)
    .FirstOrDefault(name => name == "Homer");
// null


var nonExistingStudentAge = students
    .Select(s => s.Age)
    .FirstOrDefault(age => age == 100);
// 0
Enter fullscreen mode Exit fullscreen mode

The same applies for the Last() and LastOrDefault() operators.

ElementAt

This method is used to retrieve an element at a specific index.

var fifthStudent = students.ElementAt(4);

Console.WriteLine($"Name: {fifthStudent.Name}, Age: {fifthStudent.Age}");
// Name: Farook, Age: 18
Enter fullscreen mode Exit fullscreen mode

That said, if you use an index that is not present, the exception will be thrown:

var nonExistingStudent = students.ElementAt(100);
Enter fullscreen mode Exit fullscreen mode
System.ArgumentOutOfRangeException: Index was out of range. 
Enter fullscreen mode Exit fullscreen mode

So it's safer to use ElementAtOrDefault:

var nonExistingStudent = students.ElementAtOrDefault(100); // null
Enter fullscreen mode Exit fullscreen mode

Where

The Where() operator is used to filter multiple elements. The output is an array of all elements that meet a specified criteria.

var studentsFromBosnia = students.Where(x => x.Country == "Bosnia");

foreach (var student in studentsFromBosnia)
{
    Console.WriteLine($"Name: {student.Name}, Age: {student.Age}");
}
// Name: Mirza, Age: 18
// Name: Armin, Age: 20
Enter fullscreen mode Exit fullscreen mode

One thing to note about studentsFromBosnia is that is not of type List<Student>, but rather IEnumerable<Student>. The LINQ works in either way.

However, if you need to pass this collection to a method that takes an array or a list as a parameter, you can cast an IEnumerable to derived types.

BUILDERS

To convert an IEnumerable to an array simply do:

var studentsArray = studentsFromBosnia.ToArray(); 
// Array<Student>
Enter fullscreen mode Exit fullscreen mode

To convert an IEnumerable to a list simply do:

var studentsList = studentsFromBosnia.ToList(); 
// List<Student>
Enter fullscreen mode Exit fullscreen mode

To convert an IEnumerable collection to a dictionary, first restructure the data into an anonymous key-value pair object and then covert the object to dictionary.

var studentsDictionary = studentsFromBosnia
  .Select((stud, index) => new { index, stud }) // creating an anonymous object
  .ToDictionary(x => x.index, x => x.stud); // converting object to dictionary
// Dictionary<int, Student>
Enter fullscreen mode Exit fullscreen mode

PROJECTION

Select

The Select operator is used to retrieve a list of specific properties from an IEnumerable, in this case List. For example, let's say we want to pick names of all students:

IEnumerable<string> names = students.Select(s => s.Name);

foreach (var name in names)
{
    Console.WriteLine(name); 
    // Mirza, Armin, Alan, Seid...
}
Enter fullscreen mode Exit fullscreen mode

Or a combination of properties (Name & Country):

var customList = students.Select(s => new { Name = s.Name, Country = s.Country });

foreach (var student in customList)
{
    Console.WriteLine($"Name: {student.Name}, Country: {student.Country}");
}
Enter fullscreen mode Exit fullscreen mode

custom-select

SORTING

OrderBy

The OrderBy operator is used to order elements in the collections. For example, if we'd take ages of all students and put them into a collection:

var ages = students.Select(s => s.Age);
Enter fullscreen mode Exit fullscreen mode

The outcome would be a collection sorted in the order the students were created. Applying the OrderBy, the ages are shown from smallest to largest:

var agesInOrder = students.OrderBy(s => s.Age).Select(s => s.Age);
// [18, 18, 18, 19, 19, 20, 20, 21, 22, 24]
Enter fullscreen mode Exit fullscreen mode

It's clear that we have duplicate elements. We can fix that using the Distinct() operator:

var agesInOrder = students.OrderBy(s => s.Age).Select(s => s.Age).Distint();
// [18, 19,20, 21, 22, 24]
Enter fullscreen mode Exit fullscreen mode

The OrderBy operator can also be applied after the Select:

var agesInOrder = students
    .Select(s => s.Age)
    .OrderBy(s => s) // sort by values in the collection (ages)
    .Distinct();
// [18, 19,20, 21, 22, 24]
Enter fullscreen mode Exit fullscreen mode

In this instance, the ordering by ages is still hapening, but because we've already filtered the ages in the previous line, the OrderBy has less work to do.

OrderByDescending

The OrderbyDescending is doing the same but in the reversed order:

var agesInReverseOrder = students
    .Select(s => s.Age)
    .OrderByDescending(s => s)
    .Distinct();
// [24, 22, 21, 20, 19, 18]
Enter fullscreen mode Exit fullscreen mode

ThenBy, ThenByDescending

The ThenBy operator is used to apply the additional sorting after the OrderBy.

var orderedByAgeAndCountry = students
    .OrderBy(s => s.Age)
    .ThenBy(s => s.Country)
    .Select(s => s.Name);
// ["Mirza", "Eddy", "Farook", "Abdurahman", "Alan"...]

var orderedByAgeAndCountry2 = students
    .OrderBy(s => s.Age)
    .ThenByDescending(s => s.Country)
    .Select(s => s.Name);
// ["Farook", "Eddy", "Mirza", ...]
Enter fullscreen mode Exit fullscreen mode

Next Chapters:

In the coming parts we'll dive deeper into LINQ.

Don't forget to hit the follow button. Also, follow me on Twitter to stay up to date with my upcoming content.

Bye for now 👋

Top comments (0)