In this post we will explore the following patterns for querying with ef core to make the queries more maintainable by reusing code and making the queries testable where we use them.

Specification Pattern

What is it?

The specification pattern allwos to encapsulate the Expression that is used for the LINQ query in an object that is reusable and can encapsulate some other functionality too

Why do we want the Specifications?

Simply to parametrize our query methods in a Repository without changing the Repository. It allows also for more flexible code and better testable code in general

Consider the following repository:

This allows you to create new objects for queries instead of placing queries left and right in your code. Also you can reuse the Specifications for all Queries on this Repository class.

Query Object Pattern

What is it

The query object pattern goes one step farther than the specification pattern.
You encapsulate whole queries and not only the Expressions in an object that can accept parameters (or not) and encapsulates the actual Query.
This allows to handle caching, Including of Navigationproperties and so on.
This can also go hand in hand with specification pattern and the following Unit of Work pattern.

The beauty of this is not only the reusability, but also that you can easily Mock those queries in tests and can give them descriptive names.

Even though we have  a really simple query in this example and we make heavy use of DI it is still a useable example. You can also add another ExecuteAsync method that accepts an ISpecification object to have the benefits of both worlds.

Unit of Work Pattern

What is it?

The Unit of work pattern lets you handle all the operations on the database during one scope of operation ( be it a web request for example) in a transactional manner. It ensures that you do not call the SaveChangesAsync method twice on a given DbContext, because that is something ef core does not like at all.

Create IUnitOfWork interface like so

Then let your context or repository implement this interface
inject this interface aside with your repository with DI.

And call CommitAsync after you are done with the Request. (assuming you are using a Web API)

Why use Unit of Work?

SaveChanges on a single DbContext Entity will cause an error if called multiple times
Also better performance wise
Implements optimistic Concurrency (last write wins)
Still this does not remove the need to have transactions be handled by you (see this post for more on transactions)

In tests you can also easily check if the method was called at the right point in time. For example with NSubstitutes Substitute.Received() method.

 

 

Leave a Reply