Low-level API

The Async API is mainly about QuerySpec, Linq and the Unit of Work api, but as these are build on top of the low-level API, the Async API is also available in the Low-level query API, though not all methods of the low-level API have an async variant.

We added async variants of the following groups of low-level API methods:

  • The methods used by QuerySpec, Linq and the Unit of Work classes, like fetching projections, datareaders, collections, deleting entities directly etc.
  • The methods to open a connection
  • The methods for fetching excluded fields
  • The methods for saving an entity using the low level API as this is a common pattern and it would force creating unit of work objects to use an async save.
  • The methods for deleting a collection of entities asynchronously.

There are no async methods for commit, rollback and close a connection, because there are no async methods for those operations in ADO.NET. There are also no async variants for methods which fetch a single entity, because one should use Linq or QuerySpec to do so, which do offer asynchronous single-entity fetch constructs.

To get details about the various methods, please consult the LLBLGen Pro Runtime Library Reference Manual, which is an additional download from the LLBLGen Pro website.

Unit of Work and Async

The Unit of work classes are also async aware and have been updated with async variants of some of their methods. Using the unit of work classes hasn't changed, only the way to commit the unit of work, using CommitAsync. CommitAsync is equal to Commit, however it's an asynchronous method which can be awaited and which performs all its actions asynchronously:

  • Opening the connection is asynchronous (adapter)
  • Every element to execute is executed asynchronously, except stored procedure calls and callbacks.

Transaction create, commit/rollback are synchronous as there's no ADO.NET way to do these actions asynchronously. All actions taken by the unit of work are done asynchronously and the results are awaited before the next action is performed.

Synchronous code used by asynchronous methods

Saving an entity asynchronously and refetching it will do the save part (insert or update) asynchronously but will do the fetch part synchronously. This is because the fetch uses the FetchEntity method on adapter and a call to a generated method in SelfServicing, requiring more code to be ported / copied to make things async than reasonable, so we left that synchronously.

Fetches of entities are single row returning queries using PK filters, so the queries should execute very quickly and the delay be minimal, so making this piece of the pipeline asynchronous wouldn't make things behave that differently, hence we left it as-is.

TransactionScope and Async

Although the API supports TransactionScope/System.Transactions, it's mandatory that the code which creates the TransactionScope and which has one or more await statements inside the TransactionScope, has a valid SynchronizationContext active on the current thread. If not, the TransactionScope will fail, because it's being disposed on another thread due to the await statement. This is a general aspect of asynchronous programming and .NET and not something we can avoid.