Asynchronous Repositories


The Repository pattern has long been tried and tested. There are also countless scaffolding tools for generating boilerplate repositories for established data access technologies, such as the Entity Framework. Despite improvements in tools like the Entity Framework, not much has been done to update the Repository pattern to leverage the best of LINQ and the asynchronous enhancements in the .NET Framework.

In this article, I'll describe how to improve upon the classic Repository pattern by maintaining the fidelity of the original design, while adding asynchronous support and providing LINQ query composition for more complex scenarios.

The Read-Only Repository

When I first began evaluating whether a generic interface could be extracted from the numerous, existing scaffolding examples I found, two things became abundantly clear. First, it was definitely possible to extract a simple, reusable interface. It also occurred to me that there are a number of scenarios where you might want a read-only repository; thus, the IReadOnlyRepository<T> interface was born.

The idea behind this interface is analogous to IReadOnlyList<T>. I also had the goal of making the required implementation as minimal as possible.

 

The method signatures might seem a little strange at first, but it will make sense soon enough. I attempted many variations, but this was the only one that retained LINQ query composition without requiring the repository to be disposable. It also made creating a repository for the Per-Request model easy.

You might think that only one method is needed, but as we'll see in an example later, there is a subtle difference. The first signature will match most query operations, while the second signature is used primarily for query projections to scalar results, such as an asynchronous count.

Asynchronous Queries

To further explain the method signatures, we first need to examine what it takes to execute a LINQ query asynchronously. LINQ is based on IQueryable<T>, which is based on IEnumerable<T>. Since IEnumerable<T> is not asynchronous, neither is IQueryable<T>. To solve this problem and maintain query capabilities, we use a callback function to shape the query just prior to execution. This also provides a lot of flexibility in how the query is executed. For example, if the repository is backed by the Entity Framework 6.0 or above, you can simply leverage its intrinsic asynchronous extensions. However, if you're using another queryable source, such as a DataServiceContext, you'll have to wrap and execute the query with a Task on your own. Ultimately, this approach enables asynchronous query execution regardless of whether the underlying query provider supports such operations. In a follow up post, I'll illustrate how take the concepts from the Entity Framework to make any IQueryable<T> asynchronous, even when underlying query provider doesn't directly support it.

Sample Implementation

To illustrate pulling all the pieces together, let's create a repository that is backed by the Entity Framework. Also note that this approach can work for versions earlier than 6.0, but you'll have to execute the query in your own Task.

 

Extension Methods

Up to this point, you may be wondering where all the other standard operations for a repository are. Since we now have a way to generically create and execute asynchronous LINQ queries, adding extension methods to fill in the gaps is straightforward. I chose to use extension methods because my objective was to minimize the effort required to implement the interface.

The following outlines the provided extension methods that support the standard repository operations:

Sample Usage

By now, the overall picture is solidifying; however, to really drive the point home, let's examine some sample usage scenarios of our implementation. For brevity, all of the examples will use the non-cancellable extension methods.

Retrieving All Data

The following example demonstrates how all data from the repository can be retrieved. In almost all scenarios, I'm not sure why anyone would want to allow returning all data without pagination, but since most repositories have this capability, I preserved it for consistency.

Retrieving a Single Entity

The following example demonstrates retrieving a single entity. Since each entity should be matched by a unique key and it is not an error if the repository does not have a match, the execution behavior is the same as the LINQ extension method SingleOrDefault.

Searching for Entities

The following example demonstrates how to search for entities. Unlike GetSingleAsync, this method will return a sequence of all matches.

Paging

The following example demonstrates how to paginate entity queries. The PagedCollection<T> isn't anything special; it is simply a ReadOnlyCollection<T> that also includes a TotalCount property.

Complex Queries

All of previous examples illustrate a strong affinity to the typical definition and implementation of the Repository pattern. However, what happens when you encounter a query scenario that isn't addressed? Common situations include sorting, composite predicates, and so on. In some cases, this could be internally handled by the repository implementation, but this would be limiting to the implementation and not very clear in usage. Other alternatives are possible, but many of them result in changing the signature of the interface or concrete class that is used. Since this design still incorporates the fundamental query capabilities of LINQ, we can use it to solve these issues.

While some of these complex query scenarios are undeniably verbose, you could easily create your own succinct extension methods to suit your needs without changing the core interface.

Web API and OData

Another great, real world example is using this style of repository with an OData-based ASP.NET Web API controller. Enabling an asynchronous, queryable action method for the controller is now very simple.

Writable Repositories

Before wrapping up this article, it's worth discussing a writable repository. According to Fowler, "Objects can be added to and removed from the Repository". While Fowler doesn't explicitly discuss updating items stored in a repository, it is almost certainly implied. Furthermore, if you can add, remove, and update items within a repository, then you'll need a way to persist those changes.

There is already a design pattern that is dedicated to managing the changes that must be persisted to a data store - unit of work. I'll cover the unit of work in an upcoming post, but I'll briefly describe its relationship here. A writable repository should have a collaborating unit of work that manages the underlying state changes. I could have had this interface implement the unit of work interface, but a repository is not a unit of work; rather, it uses a unit of work. Furthermore, the vernacular of the two patterns is different. For example, you commit a unit of work, but you don't commit a repository; you save the changes within a repository. For this reason, although the behaviors are similar, the repository interface does not directly implement a unit of work interface.

While Fowler does describe rolling back changes in a unit of work, he doesn't mention exposing whether it contains any changes and you may be wondering why it is here. For mostly stateless applications, such as web applications and services, it is not particularly useful. However, for stateful applications such as Windows Store applications, Windows Phone applications, and so on, this capability is particularly useful. It enables you to detect and expose when there are user changes; for example, to enable or disable a save button. When a user cancels an operation you can discard or roll back the changes.

Conclusion

In this article we examined a reimaging of the classic Repository pattern with a breath of fresh air to support asynchronous operations and complex query composition. There are many implementations of the Repository pattern and the ideas presented here are in no way anymore correct than any other. My objective was to present new ideas that, hopefully, provide more flexibility in your applications. Feel free to provide questions, comments, or feedback. In two upcoming, related posts, I'll dive into the unit of work pattern and making IQueryable<T> asynchronous.

AsyncRepositories.zip

Comments (3)

  1. Mario Majcica says:

    Hi Cris,

    I liked your example and explanation. It will be also nice to have an example of CRUD calls from let's say an OData Controller, through the repository implementation for an entity. A simple example WebAPI project using a classic Northwind Customer entity, will be clearer than thousand words.

    Thanks

  2. Chris_Martinez_77 says:

    Hi Mario,

    The more detailed CRUD pieces are covered in the next article: Unit of Work – Expanded (blogs.msdn.com/…/unit-of-work-expanded.aspx).  All the pieces to implement the code are in both articles, but this post only contains the bare minimum implementation.

    To specifically answer your question, you'd need a writable repository, which "should" be backed by a unit of work (see other article).  With this in place, performing CRUD operations in your controller are simple.  I already illustrated the Read part (see above).  For the remainder, it would look like this:

    —————————————————————————————————————–

    [ODataRouting]
    [ODataFormatting]
    public class CustomerController<Customer, int> : ApiController
    {
       private readonly IRepository<Customer> repository;
       public CustomerController( IRepository<Customer> repository )
       {
           this.repository = repository;
       }
       protected IRepository<Customer> Repository
       {
           get { return this.repository; }
       }
       [AcceptVerbs( new string[] { "PATCH", "MERGE" } )]
       public async Task<IHttpActionResult> Patch( [FromODataUri] int key, Delta<Customer> delta )
       {
           var original = await this.Repository.GetSingleAsync( c => c.CustomerId == key );
           HttpResponseMessage = null;
           if ( original == null )
           {
               response = this.Request.CreateResponse( HttpStatusCode.NotFound );
               return new ResponseMessageResult( response );
           }
           delta.Patch( original );
           this.Repository.Update( original );
           await this.Repository.SaveChangesAsync();
           response = this.Request.CreateResponse( HttpStatusCode.OK, original );
           return new ResponseMessageResult( response );
       }
       public async Task<IHttpActionResult> Post( [FromBody] Customer entity )
       {
           if ( !this.ModelState.IsValid )
           {
               return this.BadRequest();
           }
           this.Repository.Add( entity );
           await this.Repository.SaveChangesAsync();
           // TODO: set Location header; ommitted for brevity
           var response = this.Request.CreateResponse( HttpStatusCode.Created, entity );
           return new ResponseMessageResult( response );
       }
       public async Task<IHttpActionResult> Delete( [FromODataUri] int key )
       {
           var entity = await this.Repository.GetSingleAsync( c => c.CustomerId == key );
           if ( entity != null )
           {
               this.Repository.Remove( entity );
               await this.Repository.SaveChangesAsync();
           }
           return new ResponseMessageResult( this.Request.CreateResponse( HttpStatusCode.NoContent ) );
       }
    }

    —————————————————————————————————————–

    I purposely excluded POST because I included PATCH.  POST is just a simpler variant.  The code looks almost identical to POST and I trust you won't have any trouble figuring out what that would look like.

    I hope that helps.

    – Chris

  3. Mario Majcica says:

    Thanks Chris,

    I read your other blog post. Great job! I really like the way you structured repo interfaces and UoW. Especially the idea of using extension methods to concentrate the common linq query searches.

    Do you mind if I make an example app using your concept and write a word about it? For sure I will link your articles as the source.

    Keep up with good posts!

    Cheers

    Mario

Skip to main content