This recipe is an implementation of the Repository Pattern, which allows us to abstract the underlying data source and the queries used to obtain the data.
We will be using the NuGet Package Manager to install the Entity Framework Core 1 package, Microsoft.EntityFrameworkCore
. We will also be using a SQL Server database for storing the data, so we will also need Microsoft.EntityFrameworkCore.SqlServer
.
To mock interfaces and base classes, we will use Moq
.
Finally, xunit
is the package we will be using for the unit tests and dotnet-text-xunit
adds tooling support for Visual Studio. Note that the UnitTests
project is a .NET Core App 1.0 (netcoreapp1.0), that Microsoft.EntityFrameworkCore.Design
is configured as a build dependency, and Microsoft.EntityFrameworkCore.Tools
is set as a tool.
Open Using EF Core Solution from the included source code examples.
Execute the database setup script from the code samples included for this recipe. This can be found in the DataAccess
project within the Database
folder.
Create a new file in the
DataAccess
project, with this content:In the
DataAccess
project, add a new C# interface namedIBlogRepository
with the following code:In the
DataAccess
project, create a new C# class namedBlogRepository
containing the following code:We'll add a new unit test in the
UnitTests
project that defines a test for using the repository with the following code:In the
BlogController
class of theUI
project, update the usage ofBlogContext
so it usesIBlogRepository
with the following code:Finally, we need to register the
IBlogRepository
service for dependency injection so that it can be passed automatically to the HomeController's constructor. We do that in theStartup.cs
file in theUI
project, in theConfigureServices
method:
We start off with a test that defines what we hope to accomplish. We use mocking (or verifiable fake objects) to ensure that we get the behavior that we expect. The test states that any BlogRepository
function will communicate with the context to connect for the data. This is what we are hoping to accomplish, as doing so allows us to layer tests and extension points into the domain.
The usage of the repository interface is a key part of this flexible implementation as it will allow us to leverage mocks, and test the business layer, while still maintaining an extensible solution. The interface to the context is a straightforward API for all database communication. In this example, we only need to read data from the database, so the interface is very simple.
Even in this simple implementation of the interface, we see that there are opportunities to increase reusability. We could have created a method or property that returned the list of blogs, but then we would have had to modify the context and interface for every new entity. Instead, we set up the Set
method to take a generic type, which allows us to add entities to the usage of the interface without modifying the interface. We will only need to modify the implementation.
Notice that we constrained the IRepository
interface to accept only the reference types for T
, using the where T
: class constraint. We did this because value types cannot be stored using Entity Framework; if you had a base class, you could use it here to constrain the usage of the generic even further. Importantly, not all reference types are valid for T
, but the constraint is as close as we can get using C#. Interfaces are not valid because Entity Framework cannot construct them when it needs to create an entity. Instead, it will produce a runtime exception, as they are valid reference types and therefore the compiler won't complain.
Once we have the context, we need to wrap it with an abstraction. IBlogRepository
will allow us to query the data without allowing direct control over the database connection. We can hide the details of the specific implementation, the actual context object, while surfacing a simplified API for gathering data. We can also introduce specific operations for the Blog
entity here.
The other interface that we abstracted is the IDbContext
interface. This abstraction allows us to intercept operations just before they are sent to the database. This makes the untestable part of the application as thin as possible. We can, and will, test right up to the point of database connection.
We had to register the two interfaces, IDbContext
and IBlogRepository
, in the ASP.NET dependency resolver. This is achieved at startup time, so that any code that requires these services can use them. You will notice that the registration for IBlogRepository
makes use of the IDbContext
registration. This is OK, because it is a requirement for the actual implementation of BlogRepository
to rely on IDbContext
to actually retrieve the data.
Keeping the repository implementation clean requires us to leverage some principles and patterns that are at the core of object-oriented programming, but not specific to using Entity Framework. These principles will not only help us to write clean implementations of Entity Framework, but can also be leveraged by other areas of our code.
Dependency inversion is another SOLID principle. This states that all of the dependencies of an object should be clearly visible and passed in, or injected, to create the object. The benefit of this is twofold: the first is exposing all of the dependencies so the effects of using a piece of code are clear to those who will use the class. The second benefit is that by injecting these dependencies at construction, they allow us to unit test by passing in mocks of the dependent objects. Granular unit tests require the ability to abstract dependencies, so we can ensure only one object is under test.
This repository pattern gives us the perfect area for implementing a complex or global caching mechanism. If we want to persist a value into the cache at the point of retrieval, and not retrieve it again, the repository
class is the perfect location for such logic. This layer of abstraction allows us to move beyond simple implementations and start thinking about solving business problems quickly, and later extend to handle more complex scenarios as they are warranted by the requirements of the specific project. You can think of repository as a well-tested 80%+ solution. Put off anything more until the last responsible moment.
The usage of mocks is commonplace in tests because mocks allow us to verify underlying behavior without having more than one object under test. This is a fundamental piece of the puzzle for test-driven development. When you test at a unit level, you want to make sure that the level directly following the one you are testing was called correctly while not actually executing the specific implementation. This is what mocking buys us.
There are times when we need to create complex sets of queries that will be used frequently, but only by one or two objects. When this situation occurs, we want to reuse that code without needing to duplicate it for each object. This is where the where
constraint helps us. It allows us to limit generically defined behavior to an object or set of objects that share a common interface or base class. The extension possibilities are nearly limitless.