Member-only story
CQRS in .NET C#: From Meyer’s rule to separate read models (2026)
Imagine your e-commerce has been online for a while. The Order aggregate of the Domain Logic article is a good one: it guards the stock, the coupon BENVENUTO10 and the "at least one line" rule. (If you want to find out more about the Domain Logic patterns, click here.) Then the screens arrive: My orders for the customer, a filtered list for the back office, a dashboard of the products running low. All of them want flat, joined, paginated data, and the only thing we have is the aggregate:
// Naive approach - What NOT to do: load whole aggregates to show a list
public async Task<List<OrderDto>> GetMyOrdersAsync(Guid customerId, int page, int pageSize, CancellationToken cancellationToken)
{
var orders = await db.Orders
.Include(o => o.Lines)
.Where(o => o.CustomerId == customerId)
.ToListAsync(cancellationToken);
return orders
.OrderByDescending(o => o.CreatedAt)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(o => new OrderDto(o.Id, o.CreatedAt, o.Status.ToString(), o.Lines.Sum(l => l.Quantity.Value), o.Total.Amount))
.ToList();
}It works, and it’s what most of us write first. The result?
- Every request loads all the orders of the customer, with all their lines, to show a date, a status and a total.
- Paging happens in memory, after the database has sent everything.
- The change tracker watches objects that nobody will change…









