-
-
Notifications
You must be signed in to change notification settings - Fork 11
5. Asynchronous programming
All available extensions that can execute SQL have asynchronous versions with the name ending with Async
suffix.
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.
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}");
}
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}");
}
This await foreach
language construct will create an asynchronous stream that will return values from your query as they appear on your connection.
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();
- 2.1. Execute(command, params) connection extension
- 2.2. Read(command, params) connection extensions
- 2.3. Multiple(command, params) connection extension
- 3.1. Working with basic type parameters
- 3.2. Working with class and record type parameters
- 3.3. Working with tuple type parameters
- 4.1. Working with parameters
- 4.2. Working with array types
- 6. Utility extensions