CQRS with Entity Framework Core

Over the past decade, CQRS has become more popular and implementing it with Entity Framework Core makes it easy. We’re going to start off by discussing what CQRS is, different ways to implement it, and why Entity Framework Core is ideally suited to it. We’ll finish with examples of command and query implementations.

What is CQRS?

CQRS stands for Command Query Responsibility Segregation. At its heart, CQRS is the separation between commands and queries, specifically the model. The idea is that using a single unified model to handle both commands and queries results in an over complicated model. As the model tries to handle both, it becomes unable to handle either well and the model becomes increasingly complex.

Queries usually don’t use all the properties of a model anyway. This leads often to the creation of DTOs to handle specific queries, which in turn leads to mapping. I’ve written at length on this approach and my opinion of it here. While you can’t always avoid writing DTOs for queries, I would argue that more often than not they are not necessary. Which brings us to the main drawback of CQRS: by creating two sets of models we can actually end up increasing the complexity of the application rather than decreasing it. It is imperative therefore that we avoid creating DTO classes for our queries where possible, but how?

Data Project Organization

There are multiple ways you can organize your queries and commands. In all cases I would still recommend the approach of organizing everything based on the aggregate root just as you would if you were doing a standard repository pattern. My personal preference right now is to use extension methods on the aggregate root DbSet which I feel is more natural when working with EF Core and lends itself to a more functional paradigm. It also has the following additional benefits:

  • Reduces the amount of injected objects into controllers or request handlers.
  • Rather than injecting a database context into multiple command classes and ending up with multiple contexts, this allows us to work with a single context and thus a single transaction if needed. Alternatively you could pass the database context as a parameter, but that is less than ideal.
  • Keeps the entry point for all database related functions to a single object.

I place my queries and commands in separate folders in the data project. Below is a picture of the typical structure that I use:

Data Project Structure

I highly recommend placing your configuration for entities in separate class files to keep the size of the database context down. You can see a folder for projections; that’s because it’s usually a good idea to create reusable ones. This is especially true for larger objects as the projections tend to get used over and over again in different queries. Keeping them in separate classes also has the added benefit of reducing the size of your extension classes. We’ll talk about projections next.

Entity Framework Core Projections

There is no rule that says you must map your queries to a class. In fact, if you want to pull out the full performance of Entity Framework Core, you need to avoid it wherever possible as I have written about here. In our implementation of CQRS with EF Core, we’re going to write our queries as projections to anonymous types and return dynamic. This will allow us to skip the mapping in EF Core and pull out the full performance.

Let’s start off by writing our projection. In this example case we are going to get an employee without the list of accompanying documents with it.

public static class EmployeeProjection
    public static ╥Expressionß<╕Funcß<╥Employeeß, dynamic>> EmployeeWithoutDocuments
            return m => new

The code is fairly simple. We are constructing a expression based on an Employee type and returning an anonymous type. This will keep our Entity Framework Core queries from doing performance killing mapping and prevent us from having to define a specific DTO. You’ll notice in my case that I am placing the projections in their own folder within the data project. A second approach to organizing this is to place these static expressions in your domain classes. My personal preference here is the data project since I feel that these are more a data implementation concern than a modeling concern. Which one you choose will probably not affect your outcome that much, so feel free to go with whatever organization you feel most comfortable with.

Queries in CQRS

As I mentioned earlier, my preference when doing CQRS with Entity Framework Core is to create extension methods on the root aggregate DbSet. In our example this is going to be the Employee DbSet. There are two approaches to designing your queries: build more generic queries that take projections as parameters and building specifically named queries that write the projection directly into the query itself. On that note, you can also pass in expressions for your where, select, and more if you want to make even more generic queries. Below is what the query would look like if you went with specifically named queries:

public static class EmployeeQuery
    public static async Task GetEmployeeByIdWithoutDocumentsAsync(this ╥DbSetß employeeDbSet, int employeeId)
        return await employeeDbSet.Where(m => m.Id == employeeId).Select(╥EmployeeProjectionß.EmployeeWithoutDocuments).FirstOrDefaultAsync();

As you can see, placing the projection outside of the query class will significantly cut down on the size of the class over time. In order to express what this query does we end up with a longer but more descriptive name for the query. The down side to this is that we end up with a new query every time the projection changes. Here’s what it would look like if we passed it in as a paramter:

public static class EmployeeQuery
    public static async Task GetEmployeeByIdAsync(this ╥DbSetß employeeDbSet, ╥Expressionß<╕Funcß<╥Employeeß, dynamic>> projection, int employeeId)
        return await employeeDbSet.Where(m => m.Id == employeeId).Select(projection).FirstOrDefaultAsync();

More generic and thus less methods needed, but less descriptive. Which approach you go with is more of a personal preference and judgement decision. I don’t think anyone can argue that there is necessarily a wrong way of doing this. You can then use the query just like you would any other extension method:

var employee = await dbContext.Employee.GetEmployeeByIdAsync(╥EmployeeProjectionß.EmployeeWithoutDocuments, 300);

As an alternative to Entity Framework Core you could use a hybrid approach by using Dapper. Dapper is a micro ORM that will offer some modest performance increases depending on the query at the cost of needing to either write stored procedures or write your queries as SQL within the code. You can learn more about Dapper here.

Commands in CQRS

Commands in CQRS are set up just like queries. We create an extension method on the DbSet and call it just like we would the previous query. Our domain models will serve as the model for commands. You will find some people argue that your domain models are not your persistence models and therefore must be kept separate like this blog post.
This is true, but there is theory and purity, and then there is practice.

The vast majority of the time your data will line up with your domain. Building a bunch of DTOs that are nothing more than reflections is only going to complicate your command model needlessly. In instances where the data does not match up with a domain model then by all means create a DTO. Mass creating DTOs though based only on the concept of purity is going to make your life, and the lives of everyone around you, miserable. In the cases where a DTO makes sense, do everyone a favor and skip the horrible DTO suffix. Use DataModel or some other descriptive suffix that tells me immediately what the purpose of this DTO is. Below is an example of our command class for Employee:

public static class EmployeeCommand
    public static async Task UpdateEmployee(this ╥DbSetß employeeDbSet, ╥Employeeß employee)
        var existingEmployee = await employeeDbSet.FirstOrDefaultAsync(m => m.Id == employee.Id);
        existingEmployee.Name = new PersonName(employee.Name.FirstName, employee.Name.LastName);
        existingEmployee.Address = new Address(employee.Address.StreetAddress, employee.Address.City, employee.Address.State, employee.Address.ZipCode);
        existingEmployee.DateOfBirth = employee.DateOfBirth;

You’ll notice that we are creating new objects on Address and PersonName. The reason for this is that they are value objects and thus are immutable. You can read more about this here. Call this just like you would the query and then call SaveChangesAsync to commit the transaction.

Sean Leitzinger
Latest posts by Sean Leitzinger (see all)

4 Replies to “CQRS with Entity Framework Core”

  1. Sean great article,
    Can you expand what you gain by creating a dynamic object within a static class,
    Rather than creating a separate DTO.
    In both cases you need to write the fields you want to use.
    As I see things, by adding DTO object there is no effect on memory\maintenance\performance.

    1. Hi Oded, thanks for reading, and sorry for the late reply. There are a couple of differences. First, you’ll notice that my projection is not mapping to a strongly typed DTO, but instead it is dynamic. Using anonymous types is roughly twice as performant from my tests. This becomes more apparent the larger the data set and the more complex the object. It might make a good blog post to benchmark this and give concrete examples.

      The second thing is that every time you need to map to something when using DTOs you either have to create a new class and define all the properties, or you have tack on properties to an existing DTO. This exponentially increases the amount of classes and thus decreases maintainability. The other issue is that the more complex your objects are with nested children, the more difficult it becomes to keep your DTOs fine grained. If you decide to flatten your DTOs then that introduces its own issues.

Leave a Reply

Your email address will not be published. Required fields are marked *