Implement CQRS Pattern in ASP.NET Core 5

Implement CQRS Pattern in ASP.NET Core 5

Today, developers have a lot of choices in terms of choosing the architecture of their projects. One of the options is to choose a pattern called CQRS that helps developers to build a clean architecture that is easy to maintain and scalable. In this tutorial, I will try to give you an overview of the CQRS pattern and will help you understand how to use it in your ASP.NET Core projects.

What is CQRS Pattern?

Command Query Responsibility Segregation (CQRS) is first described in the book Object-Oriented Software Construction by Bertrand Meyer. It is an architectural pattern that separates the read and writes operations of your application. Read operations are called Queries and write operations are called Commands

Queries –  The operations that return data but don’t change the application state.

Commands – The operations that change the application state and return no data. These are the methods that have side effects within the application. 

Implement CQRS Pattern in ASP.NET Core 5

The CQRS pattern is a great expression of the single responsibility principle. It states that we should have separate models for Queries and Commands because in the real-world application the requirements for read operations are normally different than the write operations. If you are using separate models, then you can handle complex scenarios without worrying about disturbing the other operations. Without this separation, we can easily end up with domain models that are full of state, commands, and queries and harder to maintain over time.

Pros of CQRS Pattern

CQRS pattern offers the following advantages:

  • Separation of Concern – We have separate models for read and write operations which not only gives us flexibility but also keeps our models simple and easy to maintain. Normally, the write models have most of the complex business logic whereas the read models are normally simple.
  • Better Scalability – Reads operations often occur way more than writes so keeping queries separate than commands makes our applications highly scalable. Both read and write models can be scaled independently even by two different developers or teams without any fear of breaking anything.
  • Better performance – We can use a separate database or a fast cache e.g. Redis for read operations which can improve application performance significantly.
  • Optimized Data Models – The read models can use a schema or pre-calculated data sources that are optimized for queries. Similarly, the write models can use a schema that is optimized for data updates.

Cons of CQRS Pattern

There are also some challenges of implementing the CQRS pattern which include:

Added Complexity – The basic idea of CQRS is simple but in bigger systems sometimes it increases the complexity especially in scenarios where read operations also need to update data at the same time. Complexity also increased if we also introduce concepts like Event Sourcing.

Eventual consistency– If we use a separate database for read and a separate database for write then synchronizing the read data become a challenge. We have to make sure we are not reading the stale data.

Getting Started with CQRS Pattern in ASP.NET Core 5

I am sure now you have a basic understanding of the CQRS pattern so let’s build a real-world ASP.NET Core 5 Application to learn more about the actual implementation of this pattern. To keep things simple, I will not be creating multiple projects to separate the different layers of the application. I will soon write a complete article on implementing a Clean (Onion) Architecture in ASP.NET Core projects.

Setting up the Project

Create a new ASP.NET Core 5 MVC Web Application in Visual Studio 2019. Install the following packages in the project using the NuGet package manager.

  1. Microsoft.EntityFrameworkCore.SqlServer
  2. Microsoft.EntityFrameworkCore.Tools
  3. Microsoft.EntityFrameworkCore.Design
  4. MediatR
  5. MediatR.Extensions.Microsoft.DependencyInjection

The first three packages are required to use Entity Framework Core (Database First) approach and if you want to read more about using Entity Framework Core in ASP.NET Core projects then you can read my posts Data Access in ASP.NET Core using EF Core (Database First) and Data Access in ASP.NET Core using EF Core (Code First)

The last two packages are required to use MediatR library in ASP.NET Core. MediatR is one of the most popular libraries that has a simple, in-process implementation of Mediator pattern with no dependencies. The mediator pattern fits perfectly with CQRS in the application layer and combining Mediator with CQRS will give our application the best read-write performance, thing controllers, and the handlers that have a single responsibility.

READ ALSO:  CRUD Operations in ASP.NET Core 5 using Dapper ORM

If you want to learn more about Mediator pattern, then you can read my post Mediator Design Pattern in ASP.NET Core.

You also need to define the following database connection string in the project appsettings.json file.

appsettings.json

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=DB_SERVER; Database=FootballDb; Trusted_Connection=True; MultipleActiveResultSets=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Generating Models using EF Core (Database First)

For this post, I will be using the SQL Server database with Entity Framework Core (Database First) approach. We will be using the following Players table that has information about the football players.

A database table to perform CQRS operations

Open the Package Manager Console of the project and copy/paste the following Scaffold-DbContext command and press Enter. This command will generate the entity model classes from the database tables in the Models folder and will also generate a FootballDbContext class in the Data folder. 

Scaffold-DbContext -Connection "Server=DB_SERVER; Database= FootballDb; Trusted_Connection=True; MultipleActiveResultSets=true;" -Provider Microsoft.EntityFrameworkCore.SqlServer -OutputDir "Models" -ContextDir "Data" -Context "FootballDbContext"

We also need to configure the FootballDbContext in Startup.cs file as follows:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();

    services.AddDbContext<FootballDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); 
}

Implementing Application Services

We can directly use our DbContext in CQRS Commands and Queries but I am a big fan of Single Responsibility Principle. I want to keep all EF Core related code in a separate layer rather than polluting my Commands or Queries.

Create a Services folder in the project and define the following IPlayersService interface. The methods declared in the IPlayersService interface are self-explanatory as they are representing the basic CRUD operations we will perform on the Players table.

IPlayersService

public interface IPlayersService
{
    Task<IEnumerable<Player>> GetPlayersList();
    Task<Player> GetPlayerById(int id);
    Task<Player> CreatePlayer(Player player);
    Task<int> UpdatePlayer(Player player);
    Task<int> DeletePlayer(Player player);
}

Next, create the following PlayersService class and implement the above IPlayersService interface. We are injecting the instance of the FootballDbContext in the constructor of the PlayersService class using the Dependency Injection feature available in .NET Core. The methods defined in PlayersService are simply implementing the CRUD operations.

PlayersService

public class PlayersService : IPlayersService
{
    private readonly FootballDbContext _context;

    public PlayersService(FootballDbContext context)
    {
        _context = context;
    }

    public async Task<IEnumerable<Player>> GetPlayersList()
    {
        return await _context.Players
            .ToListAsync();
    }

    public async Task<Player> GetPlayerById(int id)
    {
        return await _context.Players
            .FirstOrDefaultAsync(x => x.Id == id);
    }

    public async Task<Player> CreatePlayer(Player player)
    {
        _context.Players.Add(player);
        await _context.SaveChangesAsync();
        return player;
    }
    
    public async Task<int> UpdatePlayer(Player player)
    {
        _context.Players.Update(player);
        return await _context.SaveChangesAsync();
    }

    public async Task<int> DeletePlayer(Player player)
    {
        _context.Players.Remove(player);
        return await _context.SaveChangesAsync();
    }
}

We also need to register our service in Startup.cs file as shown in the code snippet below:

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();

    services.AddDbContext<FootballDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddScoped<IPlayersService, PlayersService>(); 
}

Organizing Commands and Queries using CQRS Pattern

Before we start implementing the Commands and Queries using CQRS Pattern, we need to decide how we will organize all our commands and queries. This is important because, in large projects, you can have hundreds of commands and queries performing different operations and if you will not organize them properly then discovering and maintaining them will become a nightmare even for a senior developer.

One of the approaches I like is to create feature-wise folders. All CQRS commands, queries, handlers, validators related to one feature can be grouped under one folder. For example, to create all our commands and queries related to Players, we can have a Players folder and inside that folder, we can have separate folders for Commands and Queries.

CQRS Command and Queries Feature wise Organization in ASP.NET Core

Implementing Queries using CQRS Pattern

In this section, we will create two queries GetAllPlayersQuery and GetPlayerByIdQuery inside the Queries folder. You can easily guess from the names what operations these queries are going to perform. Here is the implementation of the GetAllPlayersQuery class.

GetAllPlayersQuery.cs

public class GetAllPlayersQuery : IRequest<IEnumerable<Player>>
{
    public class GetAllPlayersQueryHandler : IRequestHandler<GetAllPlayersQuery, IEnumerable<Player>>
    {
        private readonly IPlayersService _playerService;

        public GetAllPlayersQueryHandler(IPlayersService playerService)
        {
            _playerService = playerService;
        }

        public async Task<IEnumerable<Player>> Handle(GetAllPlayersQuery query, CancellationToken cancellationToken)
        {
            return await _playerService.GetPlayersList();
        }
    }
}

The GetAllPlayersQuery is implementing IRequest<T> interface available in the MediatR library and we specified thatwe want to return the IEnumerable<Player> from the query.

public class GetAllPlayersQuery : IRequest<IEnumerable<Player>>

Next, we define a nested query handler class GetAllPlayersQueryHandler that implements the IRequestHandler interface. This class can also be defined outside the main GetAllPlayersQuery class but I prefer to define it as a nested class because it makes our handler easy to discover.

public class GetAllPlayersQueryHandler : IRequestHandler<GetAllPlayersQuery, IEnumerable<Player>>

If you want to learn more about IRequest and IRequestHandler interfaces then you can read my post Mediator Design Pattern in ASP.NET Core.

The IPlayerService is injected in the constructor of GetAllPlayersQueryHandler class through dependency injection and we are calling the GetPlayersList method of the service to return players list.

public async Task<IEnumerable<Player>> Handle(GetAllPlayersQuery query, CancellationToken cancellationToken)
{
    return await _playerService.GetPlayersList();
}

Our next query GetPlayerByIdQuery will be similar to the above query but this time we will return a single Player by id.

GetPlayerByIdQuery.cs

public class GetPlayerByIdQuery : IRequest<Player>
{
    public int Id { get; set; }

    public class GetPlayerByIdQueryHandler : IRequestHandler<GetPlayerByIdQuery, Player>
    {
        private readonly IPlayersService _playerService;

        public GetPlayerByIdQueryHandler(IPlayersService playerService)
        {
            _playerService = playerService;
        }

        public async Task<Player> Handle(GetPlayerByIdQuery query, CancellationToken cancellationToken)
        {
            return await _playerService.GetPlayerById(query.Id);
        }
    }
}

Implementing Commands using CQRS Pattern

In this section, we will create the following three commands inside the Commands folder.

  • CreatePlayerCommand
  • UpdatePlayerCommand
  • DeletePlayerCommand
READ ALSO:  Mediator Design Pattern in ASP.NET Core

The CreatePlayerCommand creates a new Player inside the Handle method and then calls the CreatePlayer method of PlayerService to create a player in the database.

CreatePlayerCommand.cs

public class CreatePlayerCommand : IRequest<Player>
{
    public int? ShirtNo { get; set; }
    public string Name { get; set; }
    public int? Appearances { get; set; }
    public int? Goals { get; set; }

    public class CreatePlayerCommandHandler : IRequestHandler<CreatePlayerCommand, Player>
    {
        private readonly IPlayersService _playerService;

        public CreatePlayerCommandHandler(IPlayersService playerService)
        {
            _playerService = playerService;
        }

        public async Task<Player> Handle(CreatePlayerCommand command, CancellationToken cancellationToken)
        {
            var player = new Player()
            {
                ShirtNo = command.ShirtNo,
                Name = command.Name,
                Appearances = command.Appearances,
                Goals = command.Goals
            };

            return await _playerService.CreatePlayer(player);
        }
    }
}

The UpdatePlayerCommand fetches the player from the database using the GetPlayerById method of PlayerService class and then it updates the Player inside the Handle method and finally calls the UpdatePlayer method of PlayerService to update the player in the database.

UpdatePlayerCommand.cs

public class UpdatePlayerCommand : IRequest<int>
{
    public int Id { get; set; }
    public int? ShirtNo { get; set; }
    public string Name { get; set; }
    public int? Appearances { get; set; }
    public int? Goals { get; set; }

    public class UpdatePlayerCommandHandler : IRequestHandler<UpdatePlayerCommand, int>
    {
        private readonly IPlayersService _playerService;

        public UpdatePlayerCommandHandler(IPlayersService playerService)
        {
            _playerService = playerService;
        }

        public async Task<int> Handle(UpdatePlayerCommand command, CancellationToken cancellationToken)
        {
            var player = await _playerService.GetPlayerById(command.Id);
            if (player == null)
                return default;

            player.ShirtNo = command.ShirtNo;
            player.Name = command.Name;
            player.Appearances = command.Appearances;
            player.Goals = command.Goals;

            return await _playerService.UpdatePlayer(player);
        }
    }
}

The DeletePlayerCommand fetches the player from the database using the GetPlayerById method of PlayerService class and then the Player is deleted from the database using the DeletePlayer method of PlayerService class.

DeletePlayerCommand.cs

public class DeletePlayerCommand : IRequest<int>
{
    public int Id { get; set; }
    public int? ShirtNo { get; set; }
    public string Name { get; set; }
    public int? Appearances { get; set; }
    public int? Goals { get; set; }

    public class DeletePlayerCommandHandler : IRequestHandler<DeletePlayerCommand, int>
    {
        private readonly IPlayersService _playerService;

        public DeletePlayerCommandHandler(IPlayersService playerService)
        {
            _playerService = playerService;
        }

        public async Task<int> Handle(DeletePlayerCommand command, CancellationToken cancellationToken)
        {
            var player = await _playerService.GetPlayerById(command.Id);
            if (player == null)
                return default;

            return await _playerService.DeletePlayer(player);
        }
    }
}

Using CQRS Commands and Queries in ASP.NET Core

To use all of the above commands and queries we need to use the MediatR library inside the ASP.NET Core MVC controller so let’s create a PlayerController inside the Controllers folder. We need to inject the IMediator interface available in the MediatR library in the constructor of the controller.

PlayerController.cs

public class PlayerController : Controller
{
    private readonly IMediator _mediator;

    public PlayerController(IMediator mediator)
    {
        _mediator = mediator;
    }
}

We also need to register all queries and commands along with their handlers in Startup.cs file. The MediatR.Extensions.Microsoft.DependencyInjection package we installed at the beginning of this post has an extension method AddMediatR(Assembly)that allows us to register all Requests and RequestHandlers defined in the Assembly.

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews();

    services.AddDbContext<FootballDbContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddScoped<IPlayersService, PlayersService>();

    services.AddMediatR(Assembly.GetExecutingAssembly());
}

We are now ready to send all of the above commands and queries from PlayerController and you will notice that the action methods we will define in our controller are clean and have the minimum amount of code in them.

Implementing Players List Page

To implement a player list page, we need to create an Index action method inside PlayerController. This method will send GetAllPlayersQuery with the help of the mediator.

PlayerController.cs

public async Task<IActionResult> Index()
{
    return View(await _mediator.Send(new GetAllPlayersQuery()));
}

Here is the code of Index razor view page that display the players list

Index.cshtml

@model IEnumerable<CQRSDesignPatternDemo.Models.Player>

@{
    ViewData["Title"] = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<div class="row">
    <div class="col">
        <h1>Players</h1>
    </div>
    <div class="col text-right">
        <a asp-action="Create" class="btn btn-success">Create New</a>
    </div>
</div>

<br/>
<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Id)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.ShirtNo)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Appearances)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Goals)
            </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Id)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.ShirtNo)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Appearances)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Goals)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id = item.Id }, new { @class = "btn btn-primary" })  
                @Html.ActionLink("Details", "Details", new { id=item.Id }, new { @class = "btn btn-secondary" })  
                @Html.ActionLink("Delete", "Delete", new { id=item.Id }, new { @class = "btn btn-danger" })
            </td>
        </tr>
    }
    </tbody>
</table>

Run the project and navigate to the Player/Index page and you should see the list of players displayed on the page as shown in the screenshot below.

Implement a List Page using CQRS Quey

Implementing Players Detail Page

To view the details of a single player, implement the following Details action method. This method will send GetPlayerByIdQuery with the help of the mediator.

PlayerController.cs

public async Task<IActionResult> Details(int id)
{
    return View(await _mediator.Send(new GetPlayerByIdQuery() { Id = id }));
}

Here is the code of the Details razor view page that displays the details of a single player.

READ ALSO:  Communication between Blazor Components using EventCallback

Details.cshtml

@model CQRSDesignPatternDemo.Models.Player

@{
    ViewData["Title"] = "Details";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1>@Model.Name</h1>
<hr />
<div>
  
    <dl class="row">
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Id)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Id)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Name)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Name)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.ShirtNo)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.ShirtNo)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Appearances)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Appearances)
        </dd>
        <dt class="col-sm-2">
            @Html.DisplayNameFor(model => model.Goals)
        </dt>
        <dd class="col-sm-10">
            @Html.DisplayFor(model => model.Goals)
        </dd>
    </dl>
</div>
<div>
    @Html.ActionLink("Edit", "Edit", new { id = Model.Id }, new { @class = "btn btn-primary" }) 
    <a asp-action="Index" class="btn btn-secondary">Back to List</a>
</div>

Run the project and click the Details button of any player on the list page and you should see the selected player details on the page as shown in the screenshot below.

Implement a Detail Page using CQRS Quey

Implementing Create Player Page

To implement the create player page, we need two action methods. The first action method will display a form on the page to collect player information from the user.

PlayerController.cs

public IActionResult Create()
{
    return View();
}

The second Create method will handle HTTP POST requests and will be responsible to save the player details in the database. This method will send CreatePlayerCommand with the help of the mediator.

PlayerController.cs

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(CreatePlayerCommand command)
{
    try
    {
        if (ModelState.IsValid)
        {
            await _mediator.Send(command);
            return RedirectToAction(nameof(Index));
        }
    }
    catch (Exception ex)
    {
        ModelState.AddModelError("", "Unable to save changes.");
    }
    return View(command);
}

Here is the code of Create razor view page that displays a form to the user where the user can input the player details.

Create.cshtml

@model CQRSDesignPatternDemo.Models.Player

@{
    ViewData["Title"] = "Create";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1>Create Player</h1>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create" method="post">
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label>
                <input asp-for="Name" class="form-control" />
                <span asp-validation-for="Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ShirtNo" class="control-label"></label>
                <input asp-for="ShirtNo" class="form-control" />
                <span asp-validation-for="ShirtNo" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Appearances" class="control-label"></label>
                <input asp-for="Appearances" class="form-control" />
                <span asp-validation-for="Appearances" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Goals" class="control-label"></label>
                <input asp-for="Goals" class="form-control" />
                <span asp-validation-for="Goals" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
                <a asp-action="Index" class="btn btn-secondary">Back to List</a>
            </div>
        </form>
    </div>
</div>

Run the project and click the Create New button and you should see the following Create Player form. Add information about a new player and click Create button.

Implement Create operation using CQRS Command

You should see the player added at the end of the player list as shown in the screenshot below.

A new record added in database using CQRS Command

Implementing Edit Player Page

To implement the edit player page, we once again need two action methods. The first Edit action method will fetch the selected player details from the database and will send the player model to view to display an edit form on the page.

PlayerController.cs

public async Task<IActionResult> Edit(int id)
{
    return View(await _mediator.Send(new GetPlayerByIdQuery() { Id = id }));
}

The second Edit method will handle HTTP POST requests and will be responsible to update the player details in the database. This method will send the UpdatePlayerCommand with the help of the mediator.

PlayerController.cs

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, UpdatePlayerCommand command)
{
    if (id != command.Id)
    {
        return BadRequest();
    }

    try
    {
        if (ModelState.IsValid)
        {
            await _mediator.Send(command);
            return RedirectToAction(nameof(Index));
        }
    }
    catch (Exception ex)
    {
        ModelState.AddModelError("", "Unable to save changes.");
    }
    return View(command);
}

Here is the code of the Edit razor view page that displays an edit form with the player details.

Edit.cshtml

@model CQRSDesignPatternDemo.Models.Player

@{
    ViewData["Title"] = "Edit";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1>Edit Player</h1>

<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit" method="post">
            <input asp-for="Id" class="form-control" type="hidden" />
            <div class="form-group">
                <label asp-for="Name" class="control-label"></label>
                <input asp-for="Name" class="form-control" />
                <span asp-validation-for="Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="ShirtNo" class="control-label"></label>
                <input asp-for="ShirtNo" class="form-control" />
                <span asp-validation-for="ShirtNo" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Appearances" class="control-label"></label>
                <input asp-for="Appearances" class="form-control" />
                <span asp-validation-for="Appearances" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Goals" class="control-label"></label>
                <input asp-for="Goals" class="form-control" />
                <span asp-validation-for="Goals" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Save" class="btn btn-primary" />
                <a asp-action="Index" class="btn btn-secondary">Back to List</a>
            </div>
        </form>
    </div>
</div>

Run the project and click the Edit button for any player on the list page and you should see the selected player details on the page as shown in the screenshot below. Clicking the Save button will save the player information database.

Implement Update operation using CQRS Command

Implementing Delete Player Method

The Delete action method does not require a page. It will simply send DeletePlayerCommand with the Id of the player and you should see the player deleted from the database.

PlayerController.cs

[HttpGet]
public async Task<IActionResult> Delete(int id)
{
    try
    {
        await _mediator.Send(new DeletePlayerCommand() { Id = id });
    }
    catch (Exception ex)
    {
        ModelState.AddModelError("", "Unable to delete. ");
    }

    return RedirectToAction(nameof(Index));
}

Summary

In this post, we have covered the basics of the CQRS pattern and learned about the advantages and disadvantages of the CQRS pattern. We also learned how to use the CQRS pattern along with the Mediator pattern to implement highly scalable and maintainable code. We have learned how to perform the database CRUD operations using CQRS commands and queries in an ASP.NET Core 5 MVC Web Application. Please give your feedback in the comments section if you liked this post.

This Post Has One Comment

  1. Bakshi Bala

    Nice article!,
    However the use of MediatR is not something essential here, it adds an additional level of abstraction which can be confusing for a novice. CQRS it self is little bit off-leveled concept and not easy to digest. I would recommend a re-write of the article with both without and with MediatR pattern, so that people can understand the relevance.

Leave a Reply