LINQ

Date Published: September 18, 2020

LINQ

LINQ stands for Language Integrated Query, is pronounced "link," and provides a strongly typed and logical syntax for querying data in C#. LINQ is a single syntax for querying many types of data sources.

This post contains my notes from Essential LINQ by Charlie Calvert and Dinesh Kulkarni. Most example code comes directly from the book, and any prose taken from the book is in quotes.

What LINQ is - "The Essence of LINQ"

  • Integrated - "first class citizen of .NET languages"

    • as opposed to SQL, for example, which is more complicated to write in C# and does not have the benefits of type-checking, syntax highlighting, etc. that LINQ provides
  • Unitive - a single syntax for a variety of data sources
  • Extensible - LINQ can be extended to work with other languages and data sources
  • Declarative - LINQ queries are about what the developer wants to do, rather than how that is accomplished; i.e. LINQ queries are at a higher level

    • declarative syntax is easier to read, maintain, and scale with added complexity
  • Hierarchical - LINQ provides a good visual of data, and its syntax makes it easy to navigate hierarchical relationships
  • Composable - LINQ queries can compose and use the results of one another
  • Transformative - LINQ query results can be converted into other data sources

Useful Foundational Knowledge

Collection initializers - not really part of LINQ but useful nevertheless. This line:

List<int> list = new List<int)>() { 1, 2, 3 };

is the same as these lines:

List<int> list = new List<int>();
                 list.Add(1);
                 list.Add(2);
                 list.Add(3);

A typical query expression in LINQ looks like this:

var query = from number in list
            where number < 3
            select number;

from is always at the beginning of a LINQ query, and the final line in a LINQ query always begins with either select or group. The where keyword tells the compiler which numbers are desired; in this case, those less than 3. LINQ has many operators, but the from-where-select structure is the most commonly used in LINQ queries.

It is important that the from clause come first and the select clause last in order that the IDE and compiler can provide feedback based on the type of data being queried (which is established in the from clause).

Type inference means using the keyword var to define the query, instead of declaring it as, for example, IEnumerable<int>. Type inference is the preferred style in order to enforce strong typing, allow LINQ to take advantage of composability, in addition to several other benefits.

Anonymous types - when LINQ queries a collection of objects with the new keyword following select, an anonymous type is created. This is a simple class containing only the properties being queried. This anonymous type will be the type of the output of the query. It will have an automatically generated ToString() method that can format the output when printed. At a more basic level, anonymous types are read-only classes used when an object's type cannot be known at compile time.

Entity classes are classes that map directly to a database table. Classes are declared entity classes via the Table attribute above the class declaration:

[Table(Name = "Customers")]
class Customer
{
    [Column]
    public string attributeOne;
    [Column]
    public int attributeTwo;

    // stuff Customer class does
}

Delegates declare variables that reference a single method. The delegate type must have the same signature (output type, number of parameters, parameter type(s)) as the method it references. Generic delegates are a more flexible form of delegates, where the output and parameter types can be declared later.

Lambdas provide a simple syntax for declaring delegates, particularly when working with LINQ. The => found in a lambda expression is generally read as "goes to." A lambda declaration like this:

Func<int, int, int> myLambda = (a, b) => (a + b);

is roughly equivalent to this delegate declaration:

public static int Add(int a, int b)
{
    return a + b;
}

Func<int, int, int> myDelegate = Add;

Concurrency control is how an application maintains consistency in a database where multiple users may be making changes simultaneously. There are two approaches for this: pessimistic concurrency (ask permission), optimistic concurrency (ask forgiveness). LINQ to SQL relies on optimistic concurrency, as it is far more common.

LINQ Clauses

Seven types of clauses exist in LINQ: from, let, where, orderby, join, select, and group-by.

These clauses must be called in a specific order. The first line of a LINQ query consists of a from clause. The last line of a LINQ query consists of either a select clause or a group-by clause. The body of the query consists of any combination (including zero) of from, let, where, orderby, and/or join clauses.

Sometimes a group-by clause may appear in the middle of a query. This creates a divide in the query; range variables from one side of the query cannot cross this divide to the other side of the query.

While the select clause simply outputs all members of the data source (aside from those filtered out by other clauses in the query), the group-by clause does the same thing but avoids repeating elements which share a certain key. For example:

List<animal> list = new List<animal> { cat, dog, fish, horse, cat }

var query = from animal in list
            group animal by animal.Type;

foreach (var item in query)
{
    Console.WriteLine(item.Key)
}

Now, this code prints "cat" only once, even though there are two cats in the list.

LINQ Nomenclature

computation or result sequence - the result of a LINQ query

projection - the part following select in a select clause

range variables - the variable introduced in a from clause, which need not be formally declared since its type is inferred. The range variable in the following query is word:

var query = from word in list
            select word;

A range variable's type can be explicitly declared when necessary, but this should be avoided.

LINQ to SQL

"LINQ to SQL lets developers access relational data as strongly typed objects by translating LINQ to SQL."

Essentially, LINQ provides a means of accessing SQL from C# (or another language that supports LINQ) and gives the benefits of object-oriented programming and strong typing to SQL queries. Thus, C# developers need not embed their SQL queries in string literals.

One last note

  • Visual Studio provides the Object Relational Designer, which helps with mapping tables to classes

Thanks for reading! I hope you find this and other articles here at ilyanaDev helpful! Be sure to follow me on Twitter @ilyanaDev.

ilyanaDev

Copyright © 2021