Deferred execution

One important thing to remember when working with LINQ to SQL is the deferred execution of LINQ.

The standard query operators differ in the timing of their execution, depending on whether they return a singleton value or a sequence of values. Those methods that return a singleton value (for example, Average and Sum) execute immediately. Methods that return a sequence defer the query execution, and return an enumerable object. These methods do not consume the target data until the query object is enumerated. This is known as deferred execution.

In the case of the methods that operate on in-memory collections, that is, those methods that extend IEnumerable<(Of <(T>)>), the returned enumerable object captures all of the arguments that were passed to the method. When that object is enumerated, the logic of the query operator is employed, and the query results are returned.

In contrast, methods that extend IQueryable<(Of <(T>)>) do not implement any querying behavior, but build an expression tree that represents the query to be performed. The query processing is handled by the source IQueryable<(Of <(T>)>) object.

Checking deferred execution with SQL profiler

There are two ways to check when a query has been executed. The first is:

  1. Open Profiler (All ProgramsMicrosoft SQL Server 2005(or 2008)Performance ToolsSQL 2005(or 2008) Profiler).
  2. Start a new trace on the Northwind database engine.
  3. Debug the program.

For example, when the following statement is executed, there is nothing in the Profiler:

IEnumerable<Product> beverages = from p in db.Products
where p.Category.CategoryName == "Beverages"
orderby p.ProductName
select p;

However, when the following statement is being executed, you will see from the profiler that a query has been executed in the database:

Console.WriteLine("There are {0} Beverages", beverages.Count());

The query executed in the database is like this:

exec sp_executesql N'SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
FROM [dbo].[Products] AS [t0]
LEFT OUTER JOIN [dbo].[Categories] AS [t1] ON [t1].[CategoryID] = [t0].[CategoryID]
WHERE [t1].[CategoryName] = @p0
ORDER BY [t0].[ProductName]',N'@p0 nvarchar(9)',@p0=N'Beverages'

The profiler window should look as shown the following image:

Checking deferred execution with SQL profiler

From the Profiler, we know that, under the hood, LINQ actually called sp_executesql, and it also used a left outer join to get the categories of products.

Checking deferred execution with SQL logs

Another way to trace the execution time of a LINQ statement is to use the logs. The DataContext class provides a method to log every SQL statement it executes. To see the logs, we can first add the following statement to the beginning of the program, immediately after the Main statement:

db.Log = Console.Out;

Then, we can add the following statement immediately after the variable beverages is defined, but before its count is referenced:

Console.WriteLine("After query syntax is defined, before it is referenced.");

So the first few lines of the program are now:

static void Main(string[] args)
{
// log database query statements to stand out
db.Log = Console.Out;
// CRUD operations on tables
TestTables();
Console.ReadLine();
}
static void TestTables()
deferred execution, LINQ to SQLchecking, SQL logs used{
// retrieve all Beverages
IEnumerable<Product> beverages = from p in db.Products
where p.Category.CategoryName == "Beverages"
orderby p.ProductName
select p;
Console.WriteLine("After query syntax beverages is defined, before it is referenced.");
Console.WriteLine("There are {0} Beverages", beverages.Count());
// rest of the file

Now, if you run the program, the output will look like this:

Checking deferred execution with SQL logs

From the logs, we see that the query is not executed when the query syntax is defined. Instead, it is executed when beverages.Count() is being called.

Deferred execution for singleton methods

If the query expression will return a singleton value, the query will be executed as soon as it is defined. For example, we can add this statement to get the average price of all products:

decimal? averagePrice = (from p in db.Products
select p.UnitPrice).Average();
Console.WriteLine("After query syntax averagePrice is defined, before it is referenced.");
Console.WriteLine("The average price is {0}", averagePrice);

The output is like this:

Deferred execution for singleton methodsdeferred execution, LINQ to SQLchecking, SQL logs used

From this output, we know that the query is executed at the same time as the query syntax is defined.

Deferred execution for singleton methods within sequence expressions

However, just because a query is using one of the singleton methods such as sum, average, or count, this doesn't mean that the query will be executed as soon as it is defined. If the query result is a sequence, the execution will still be deferred. The following is an example of this kind of query:

// deferred execution2
var cheapestProductsByCategory =
from p in db.Products
group p by p.CategoryID into g
select new
{
CategoryID = g.Key,
CheapestProduct =
(from p2 in g
where p2.UnitPrice == g.Min(p3 => p3.UnitPrice)
select p2).FirstOrDefault()
};
Console.WriteLine("Cheapest products by category:");
foreach (var p in cheapestProductsByCategory)
{
Console.WriteLine("categery {0}: product name: {1} price: {2}", p.CategoryID, p.CheapestProduct.ProductName, p.CheapestProduct.UnitPrice);
}

If you run the above query, you will see that it is executed when the result is being printed, and not when the query is being defined. An extract of the results looks like this:

Deferred execution for singleton methods within sequence expressionsdeferred execution, LINQ to SQLfor singleton methods

From this output, you can see that when the result is being printed, it first goes to the database to get the minimum price for each category. Then, for each category, it goes to the database again to get the first product with that price. In a real application, you probably wouldn't want to write such a complex query in your code. So, you would put it in a stored procedure.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset