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.
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.
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.
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:
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.
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.
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.
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.
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.