Skip to main content
SEMastery

Stop Conflating CQRS and MediatR: They Are Not the Same Thing

CQRS and MediatR are two different ideas. Learn what each one really does, why people mix them up, and how to use CQRS in .NET with or without MediatR.

12 min readUpdated March 11, 2026

Two different tools in the same toolbox

Think about your kitchen at home.

A knife cuts vegetables. A gas stove heats food. Both live in the same kitchen. You often use them together to cook one meal. But they are not the same thing. A knife does not heat food. A stove does not cut onions.

Now imagine someone says, "A knife is cooking." That sounds silly, right? A knife is just one tool you use while cooking. You can cook without that exact knife. You can use the knife for things that are not cooking at all.

This is exactly what happens with CQRS and MediatR in the .NET world.

Many developers say "CQRS" but they really mean "MediatR." They have used them together so many times that the two words got glued in their heads. But CQRS is the idea, like cooking. MediatR is just one tool, like the knife. You can have one without the other.

Let us slowly pull them apart so you can see each one clearly.

What CQRS really is

CQRS stands for Command Query Responsibility Segregation. That is a big phrase. Let us break it down.

  • A command is when you want to change something. "Place this order." "Update my address." "Delete this comment."
  • A query is when you want to read something. "Show my orders." "What is my balance?" "List the top books."
  • Segregation just means keep them apart.

So CQRS says one simple thing: the code that changes data should be separate from the code that reads data.

That is it. CQRS is not a library. You cannot install CQRS from NuGet. It is a way of thinking about your code.

Here is the shape of the idea.

CQRS splits one model into a write side and a read side.

Why would you want this split? Because reads and writes have different needs.

When you write, you care about rules. Is the order valid? Does the user have enough money? You want safety and care.

When you read, you care about speed. You just want to show data on the screen fast. You often do not need the full, heavy model with all its rules.

By keeping the two sides apart, each side can be as simple or as smart as it needs to be.

CQRS does not mean two databases

A common myth: "CQRS means I need two databases." No. Most teams use one normal database.

CQRS is mainly about splitting reads and writes in your code. Using a separate read database is an advanced add-on. It is useful for very large systems, often together with event sourcing. But you do not need it to say you are "doing CQRS."

Levels of CQRS

Split classes
Split models
Split storage

Steps

1

Split classes

Command and query classes are separate. One database.

2

Split models

Write model has rules. Read model is a thin DTO.

3

Split storage

Separate read database, kept in sync. Advanced only.

You can stop at any level. Most apps stay at level 1.

What MediatR really is

Now meet the other tool.

MediatR is a small .NET library. It is an implementation of the Mediator design pattern. Its job is to sit in the middle of your app and pass messages around so that classes do not have to call each other directly.

Here is how it works in plain words:

  1. You create a small request object that holds some data.
  2. You send that request through MediatR.
  3. MediatR finds the one handler that knows how to deal with that request.
  4. The handler does the work and returns a result.

The caller never sees the handler. It only knows about MediatR. This is called decoupling. The pieces are loosely connected.

MediatR passes a request to exactly one handler, so the caller and handler never meet directly.

Notice something important: nothing here says anything about reads or writes. MediatR does not care if your request changes data or reads data. It just delivers the message. You could use MediatR for sending emails, for logging, for anything.

So MediatR is a delivery system. CQRS is a way to organize. They are doing two different jobs.

A tiny MediatR example

Here is what a query looks like when you use MediatR.

// The request: a small message asking for data.
public record GetOrderById(int OrderId) : IRequest<OrderDto>;
 
// The handler: the one place that knows how to answer this request.
public class GetOrderByIdHandler : IRequestHandler<GetOrderById, OrderDto>
{
    private readonly AppDbContext _db;
 
    public GetOrderByIdHandler(AppDbContext db) => _db = db;
 
    public async Task<OrderDto> Handle(GetOrderById request, CancellationToken ct)
    {
        var order = await _db.Orders
            .Where(o => o.Id == request.OrderId)
            .Select(o => new OrderDto(o.Id, o.Total))
            .FirstOrDefaultAsync(ct);
 
        return order!;
    }
}

And the controller just sends the message:

[ApiController]
[Route("orders")]
public class OrdersController : ControllerBase
{
    private readonly IMediator _mediator;
 
    public OrdersController(IMediator mediator) => _mediator = mediator;
 
    // Route is GET /orders/{id} (wrapped in code so MDX does not choke on braces)
    [HttpGet("{id}")]
    public async Task<IActionResult> Get(int id)
    {
        var result = await _mediator.Send(new GetOrderById(id));
        return Ok(result);
    }
}

The controller does not know which class handles the request. It only talks to MediatR. That is the mediator pattern at work.

Why people mix them up

So if they are different, why do so many people glue them together?

The reason is simple: they fit nicely together. MediatR has a request and a handler. CQRS has a command and a query. A command can be a MediatR request. A query can be a MediatR request. The shapes match.

Because of this neat fit, almost every CQRS tutorial in .NET uses MediatR. New developers read those tutorials, see the two always together, and decide they are the same thing. The habit spreads.

Here is the truth side by side.

QuestionCQRSMediatR
What is it?An architectural ideaA .NET library
Main jobSeparate reads from writesPass a message to one handler
Can you install it?No, it is a way of thinkingYes, from NuGet
Cares about reads vs writes?Yes, that is its whole pointNo, it does not care
Needed for the other?NoNo

Read that last row again. Neither one needs the other. That is the key idea of this whole post.

The four honest combinations

Both
CQRS only
MediatR only
Neither

Steps

1

Both

Split reads and writes, deliver them through MediatR.

2

CQRS only

Split reads and writes, call handlers directly.

3

MediatR only

Use the mediator, but do not separate reads and writes.

4

Neither

A small app with plain services. Totally fine.

All four are valid. Pick what fits your app.

CQRS without MediatR

Let us prove the point. We can do clean CQRS with zero libraries. We keep our command and query classes separate, and we call their handlers directly through the dependency injection container.

First, define small interfaces of our own:

// Our own tiny contracts. No external library needed.
public interface ICommand<TResult> { }
public interface IQuery<TResult> { }
 
public interface ICommandHandler<TCommand, TResult>
    where TCommand : ICommand<TResult>
{
    Task<TResult> Handle(TCommand command, CancellationToken ct);
}
 
public interface IQueryHandler<TQuery, TResult>
    where TQuery : IQuery<TResult>
{
    Task<TResult> Handle(TQuery query, CancellationToken ct);
}

Now a query and its handler look almost the same as before, but with our interfaces:

public record GetOrderById(int OrderId) : IQuery<OrderDto>;
 
public class GetOrderByIdHandler : IQueryHandler<GetOrderById, OrderDto>
{
    private readonly AppDbContext _db;
 
    public GetOrderByIdHandler(AppDbContext db) => _db = db;
 
    public async Task<OrderDto> Handle(GetOrderById query, CancellationToken ct)
    {
        return await _db.Orders
            .Where(o => o.Id == query.OrderId)
            .Select(o => new OrderDto(o.Id, o.Total))
            .FirstOrDefaultAsync(ct) ?? throw new KeyNotFoundException();
    }
}

The controller injects the handler directly:

[HttpGet("{id}")]
public async Task<IActionResult> Get(
    int id,
    IQueryHandler<GetOrderById, OrderDto> handler,
    CancellationToken ct)
{
    var result = await handler.Handle(new GetOrderById(id), ct);
    return Ok(result);
}

Look closely. We still have commands and queries kept apart. That is real CQRS. But there is no MediatR anywhere. The reads still live in query handlers. The writes would live in command handlers. The split is complete.

This diagram shows how the request flows when you own the dispatch:

With your own handlers, the call goes straight to the right handler. No mediator in the middle.

If you have many handlers and you do not want to inject each one by hand, you can write a small dispatcher of your own. It is usually under fifty lines. That gives you the "send a message" feel of MediatR while keeping full control and no license cost.

When MediatR is worth it (and when it is not)

MediatR is not bad. It is a fine tool. The mistake is using it without thinking, just because a tutorial did.

Here is an honest guide.

SituationGood fit for MediatR?
You want pipeline behaviors for logging, validation, cachingYes, this is its strength
You have a large app with hundreds of handlersOften yes, less wiring
You have a small app with a few endpointsUsually no, it adds noise
You want to learn how dispatch really worksNo, build your own first
Licensing for a big company is a concernWeigh the cost carefully

The biggest real benefit of MediatR is its pipeline behaviors. These let you wrap every request with shared steps, like validation or logging, in one place. If you lean on those a lot, MediatR earns its keep. If you do not, you may be paying complexity for nothing.

The licensing change you should know about

In July 2025, MediatR (and AutoMapper) moved to a commercial license under a new company, Lucky Penny Software, created by the original author Jimmy Bogard.

There is a free Community edition for individuals, learning, non-profits, and small companies under a revenue limit. Bigger companies usually need a paid plan, priced by team size. Older MIT-licensed versions still exist and are archived, but new versions follow the commercial terms.

This is not a reason to panic. But it is a reason to be honest about whether you truly need MediatR. If you were only using it because "that is how you do CQRS," now you know better. You can do CQRS without it.

A clear decision path

Here is a simple way to choose, step by step.

A short decision path for choosing between CQRS, MediatR, both, or neither.

Walk it slowly:

  1. Is your app tiny with just a few endpoints? Use plain services. You may not need CQRS or MediatR at all.
  2. Will your app grow and have clear read and write paths? Then split them. That is CQRS.
  3. Once you have CQRS, ask: do you need shared pipeline steps like validation on every request? If yes, MediatR is a good fit. If no, write a small dispatcher you own.

Notice that CQRS comes before the MediatR question. That is the right order. First decide how to organize. Then pick the delivery tool.

Common mistakes to avoid

A few traps that students fall into:

  • Calling MediatR "CQRS." Now you know they are different. Use the right word.
  • Adding MediatR to a tiny app. If you have five endpoints, the extra layers just make the code harder to follow.
  • Thinking CQRS needs two databases. It does not. One database is the normal case.
  • Putting business rules in query handlers. Queries should mostly just read. Keep the heavy rules on the command side.
  • Using MediatR only to feel "clean." Cleanliness comes from clear separation, not from one library.

Quick recap

  • CQRS is an idea: keep the code that changes data separate from the code that reads data.
  • MediatR is a library: it passes a request to exactly one handler. It is the mediator pattern.
  • They fit together well, so people glued them in their minds. But they are not the same thing.
  • You can do CQRS without MediatR using your own small interfaces or a tiny dispatcher.
  • You can use MediatR without CQRS, because MediatR does not care about reads versus writes.
  • MediatR's strongest feature is its pipeline behaviors for shared steps like validation and logging.
  • MediatR went commercial in July 2025 under Lucky Penny Software, with a free Community tier and paid plans for larger companies.
  • Decide your architecture first (CQRS or not), then pick your delivery tool (MediatR, your own dispatcher, or direct calls).

References and further reading

Related Patterns