Skip to content

5. Asynchronous programming

Vedran Bilopavlović edited this page May 27, 2021 · 7 revisions

Working with asynchronous extensions

All available extensions that can execute SQL have asynchronous versions with the name ending with Async suffix.

ExecuteAsync and SingleAsync

All asynchronous overloads of ExecuteAsync and SingleAsync extensions are returning ValueTask that represents an asynchronous operation that can return a value.

Anyone familiar with asynchronous programming in .NET will have no problem using these extensions, just follow standard async await concept.

ReadAsync and QueryAsync

All asynchronous overloads of ReadAsync and QueryAsync are NOT returning Task or ValueTask.

Instead, they will build an asynchronous iterator that returns IAsyncEnumerable instead.

IAsyncEnumerable interface allows for asynchronous streaming - which you can use in Norm to create an asynchronous stream from your database.

Examples:

// Asynchronously stream values directly from database
await foreach(var (orderId, productId, quantity) in connection.ReadAsync<int, int, int>("SELECT TOP 10 OrderId, ProductId, Quantity FROM OrderDetails"))
{
    Console.WriteLine($"order={orderId}, product={productId} with quantity {quantity}");
}

Try it yourself

public record OrderDetail(int OrderId, int ProductId, int Quantity);
//...

// Asynchronously stream instances directly from database
await foreach(var d in connection.ReadAsync<OrderDetail>("SELECT TOP 10 OrderId, ProductId, Quantity FROM OrderDetails"))
{
    Console.WriteLine($"order={d.OrderId}, product={d.ProductId} with quantity {d.Quantity}");
}

Try it yourself

This await foreach language construct will create an asynchronous stream that will return values from your query as they appear on your connection.

Using LINQ with IAsyncEnumerable returned from ReadAsync and QueryAsync

Synchronous extensions are returning normal enumerators which means - you can use the standard LINQ library to build an expression tree for your enumerator.

Most common examples are creating a list, creating a dictionary, or just using the first or default value:

using System.Linq;
using Norm;

// list of instances
var list = await connection.ReadAsync<Customer>("SELECT * FROM Customers").ToListAsync();
		
// dictionary of values
var dict = await connection.ReadAsync<(int id, string value)>("select CustomerID, CustomerName from Customers").ToDictionaryAsync(t => t.id, t => t.value);
		
// first or default value
var id = await connection.ReadAsync<int>("select CustomerID from Customers").FirstOrDefaultAsync();

However, asynchronous versions QueryAsync and ReadAsync do not return standard enumerator IEnumerable but IAsyncEnumerable - so we can't use standard LINQ expressions.

The solution is to reference an System.Linq.Async into the project.

This library is created and supported by the ".NET Foundation and Contributors" and it provides support for Language-Integrated Query (LINQ) over IAsyncEnumerable<T> sequences. It implements - all the same extensions as the standard LINQ library.

And it also uses the same namespace.

The same example from above would look something like this:

using System.Linq;
using Norm;

// list of instances
var list = await connection.ReadAsync<Customer>("SELECT * FROM Customers").ToListAsync();
		
// dictionary of values
var dict = await connection.ReadAsync<(int id, string value)>("select CustomerID, CustomerName from Customers").ToDictionaryAsync(t => t.id, t => t.value);
		
// first or default value
var id = await connection.ReadAsync<int>("select CustomerID from Customers").FirstOrDefaultAsync();

Try it yourself

See also