A Guide to LINQ in C#: Querying Any Collection

An introduction to LINQ (Language-Integrated Query) in C#. Learn how to use its powerful and expressive syntax to query in-memory collections, databases, and other data sources.

One of the most powerful and beloved features of C# is LINQ (Language-Integrated Query). LINQ is a set of technologies that integrates query capabilities directly into the C# language. It provides a consistent, expressive, and powerful way to query data from a variety of sources, including in-memory collections, databases, XML documents, and more.

If you're a C# developer, mastering LINQ is not optional; it's an essential skill for writing clean and efficient data manipulation code.

The Problem Before LINQ

Before LINQ, if you wanted to query data from a collection, you had to write foreach loops with if statements. If you wanted to query a database, you had to write SQL strings. Each data source had its own separate way of being queried. LINQ provides a unified model for querying any data source that implements the IEnumerable<T> interface.

The Two Syntaxes of LINQ

LINQ has two different syntaxes that you can use, which are compiled to the exact same code.

  1. Query Syntax (or Declaration Syntax): This syntax looks very similar to SQL.
  2. Method Syntax (or Fluent Syntax): This syntax uses a chain of extension methods.

While you can use either, the C# community has largely standardized on using Method Syntax for its fluency and composability.

LINQ in Action: Using Method Syntax

Let's look at some of the most common LINQ operators using a simple collection of Product objects.

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Category { get; set; }
    public decimal Price { get; set; }
}

var products = new List<Product>
{
    new Product { Id = 1, Name = "Laptop", Category = "Electronics", Price = 1200.00m },
    new Product { Id = 2, Name = "Keyboard", Category = "Electronics", Price = 80.00m },
    new Product { Id = 3, Name = "T-Shirt", Category = "Apparel", Price = 25.00m },
    new Product { Id = 4, Name = "Jeans", Category = "Apparel", Price = 60.00m },
};

Filtering with Where

The Where method filters a sequence based on a predicate (a function that returns a boolean).

// Find all products in the 'Electronics' category
var electronics = products.Where(p => p.Category == "Electronics");

Projecting with Select

The Select method projects each element of a sequence into a new form. It's used to transform the data.

// Get just the names of all the products
var productNames = products.Select(p => p.Name);

Chaining Methods

The real power of LINQ comes from chaining these methods together.

// Get the names of all apparel products that cost more than $50
var expensiveApparelNames = products
    .Where(p => p.Category == "Apparel" && p.Price > 50.00m)
    .Select(p => p.Name);

This is incredibly readable. It clearly states the intent of the query.

Other Common LINQ Methods

  • OrderBy / OrderByDescending: Sorts the elements of a sequence.

    var sortedByPrice = products.OrderByDescending(p => p.Price);
    
  • First / FirstOrDefault: Returns the first element of a sequence. First will throw an exception if the sequence is empty, while FirstOrDefault will return the default value (null for reference types).

    var firstProduct = products.First(p => p.Category == "Electronics");
    
  • Single / SingleOrDefault: Similar to First, but it throws an exception if there is more than one element that matches the condition.

  • Any: Checks if any element in the sequence satisfies a condition.

    bool hasCheapProducts = products.Any(p => p.Price < 10.00m);
    
  • All: Checks if all elements in the sequence satisfy a condition.

  • Count: Counts the number of elements in a sequence.

  • Sum / Average / Max / Min: Perform aggregate calculations on a sequence.

    decimal totalValue = products.Sum(p => p.Price);
    

Deferred Execution

One of the most important concepts to understand about LINQ is deferred execution. Most LINQ queries are not executed when they are defined. They are only executed when the results are actually enumerated.

var electronicsQuery = products.Where(p => p.Category == "Electronics"); // Query is NOT run here

// The query is executed here, when we loop through it.
foreach (var product in electronicsQuery)
{
    Console.WriteLine(product.Name);
}

Methods that force immediate execution include ToList(), ToArray(), Count(), First(), and the aggregate methods.

LINQ to SQL

The beauty of LINQ is that the same syntax works for other data sources. When you use LINQ with a database via an ORM like Entity Framework, your LINQ query is translated into SQL and executed on the database server. This allows you to write strongly-typed, compile-checked queries for your database.

Conclusion

LINQ is a transformative feature of C#. It provides a single, unified way to work with data that is both powerful and highly readable. By replacing complex loops and conditional logic with a fluent chain of query operators, LINQ allows you to express your intent clearly and concisely, leading to code that is easier to write, read, and maintain.