Skip to main content
Queries represent read operations that retrieve data without modifying system state. In this architecture, queries follow the CQRS pattern using MediatR and support pagination.

Core Interfaces

IRequestQuery

Base interface for queries that don’t return a value.
namespace Core.Application
{
    public interface IRequestQuery : IRequest
    {
    }
}
Source: Core.Application.ComandQueryBus/Queries/IRequestQuery.cs:5

IRequestQuery<TResponse>

Base interface for queries that return a value.
namespace Core.Application
{
    public interface IRequestQuery<out TResponse> : IRequest<TResponse>
    {
    }
}
Source: Core.Application.ComandQueryBus/Queries/IRequestQuery.cs:9 Type Parameters:
  • TResponse - The type of data returned by the query

IRequestQueryHandler<TRequest, TResponse>

Interface for query handlers that process queries and return results.
namespace Core.Application
{
    public interface IRequestQueryHandler<TRequest, TResponse> : IRequestHandler<TRequest, TResponse>
        where TRequest : IRequest<TResponse>
    {
    }
}
Source: Core.Application.ComandQueryBus/Queries/IRequestQueryHandler.cs:10 Type Parameters:
  • TRequest - The query type
  • TResponse - The response type
Methods:
  • Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken) - Processes the query

Pagination Support

QueryRequest<TResponse>

Base class for paginated queries.
namespace Core.Application
{
    public class QueryRequest<TResponse> : IRequestQuery<TResponse>
        where TResponse : class
    {
        public uint PageIndex { get; set; }
        public uint PageSize { get; set; }
    }
}
Source: Core.Application.ComandQueryBus/Queries/QueryRequest.cs:3 Properties:
  • PageIndex - Zero-based page index
  • PageSize - Number of items per page

QueryResult<TEntity>

Wraps query results with pagination metadata.
namespace Core.Application
{
    public class QueryResult<TEntity>
        where TEntity : class
    {
        public long Count { get; private set; }
        public IEnumerable<TEntity> Items { get; private set; }
        public uint PageIndex { get; private set; }
        public uint PageSize { get; private set; }

        public QueryResult(IEnumerable<TEntity> items, long count, uint pageSize, uint pageIndex)
        {
            Items = items;
            Count = count;
            PageIndex = pageIndex;
            PageSize = pageSize;
        }
    }
}
Source: Core.Application.ComandQueryBus/Queries/QueryResult.cs:3 Properties:
  • Items - The collection of entities for the current page
  • Count - Total number of items across all pages
  • PageIndex - Current page index
  • PageSize - Number of items per page

Query Examples

GetAllDummyEntitiesQuery

Query to retrieve all entities with pagination.
using Application.DataTransferObjects;
using Core.Application;

namespace Application.UseCases.DummyEntity.Queries.GetAllDummyEntities
{
    public class GetAllDummyEntitiesQuery : QueryRequest<QueryResult<DummyEntityDto>>
    {
    }
}
Source: Application/UseCases/DummyEntity/Queries/GetAllDummyEntities/GetAllDummyEntitiesQuery.cs:6 Returns: QueryResult<DummyEntityDto> with pagination metadata Inherited Properties:
  • PageIndex - Page to retrieve
  • PageSize - Items per page

GetDummyEntityByQuery

Query to retrieve a single entity by ID.
using Application.DataTransferObjects;
using Core.Application;
using System.ComponentModel.DataAnnotations;

namespace Application.UseCases.DummyEntity.Queries.GetDummyEntityBy
{
    public class GetDummyEntityByQuery : IRequestQuery<DummyEntityDto>
    {
        [Required]
        public string DummyIdProperty { get; set; }

        public GetDummyEntityByQuery()
        {
        }
    }
}
Source: Application/UseCases/DummyEntity/Queries/GetDummyEntityBy/GetDummyEntityByQuery.cs:7 Returns: DummyEntityDto or null if not found

Handler Implementation Patterns

Collection Query Handler

Handler for retrieving multiple entities with pagination.
using Application.DataTransferObjects;
using Application.Repositories;
using Core.Application;

namespace Application.UseCases.DummyEntity.Queries.GetAllDummyEntities
{
    internal class GetAllDummyEntitiesHandler : IRequestQueryHandler<GetAllDummyEntitiesQuery, QueryResult<DummyEntityDto>>
    {
        private readonly IDummyEntityRepository _context;

        public GetAllDummyEntitiesHandler(IDummyEntityRepository context)
        {
            _context = context ?? throw new ArgumentNullException(nameof(context));
        }

        public async Task<QueryResult<DummyEntityDto>> Handle(
            GetAllDummyEntitiesQuery request, 
            CancellationToken cancellationToken)
        {
            // 1. Retrieve all entities from repository
            IList<Domain.Entities.DummyEntity> entities = await _context.FindAllAsync();

            // 2. Map domain entities to DTOs and wrap in QueryResult
            return new QueryResult<DummyEntityDto>(
                entities.To<DummyEntityDto>(),  // Mapped items
                entities.Count,                  // Total count
                request.PageIndex,               // Current page
                request.PageSize                 // Page size
            );
        }
    }
}
Source: Application/UseCases/DummyEntity/Queries/GetAllDummyEntities/GetAllDummyEntitiesHandler.cs:7

Handler Workflow

  1. Dependency Injection - Inject repository (typically read-only)
  2. Data Retrieval - Query repository using async methods
  3. Mapping - Transform domain entities to DTOs
  4. Pagination - Wrap results in QueryResult<T> if applicable
  5. Return - Return DTOs or null (never domain entities)

Single Entity Query Pattern

While not fully shown in the codebase, a typical single-entity handler would look like:
internal class GetDummyEntityByHandler : IRequestQueryHandler<GetDummyEntityByQuery, DummyEntityDto>
{
    private readonly IDummyEntityRepository _context;

    public GetDummyEntityByHandler(IDummyEntityRepository context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }

    public async Task<DummyEntityDto> Handle(
        GetDummyEntityByQuery request, 
        CancellationToken cancellationToken)
    {
        // 1. Retrieve entity by ID
        var entity = await _context.FindOneAsync(request.DummyIdProperty);
        
        if (entity == null)
            return null; // Or throw EntityNotFoundException

        // 2. Map to DTO and return
        return entity.To<DummyEntityDto>();
    }
}

Pagination Patterns

Using QueryRequest

Inherit from QueryRequest<T> for automatic pagination support:
public class GetProductsQuery : QueryRequest<QueryResult<ProductDto>>
{
    // Additional filter properties can be added
    public string Category { get; set; }
    public decimal? MinPrice { get; set; }
    public decimal? MaxPrice { get; set; }
}

Building QueryResult

Always provide complete pagination metadata:
public async Task<QueryResult<ProductDto>> Handle(
    GetProductsQuery request, 
    CancellationToken cancellationToken)
{
    // Apply filters
    var filtered = await _context.FindAllAsync(p => 
        (string.IsNullOrEmpty(request.Category) || p.Category == request.Category) &&
        (!request.MinPrice.HasValue || p.Price >= request.MinPrice) &&
        (!request.MaxPrice.HasValue || p.Price <= request.MaxPrice)
    );

    long totalCount = filtered.Count;

    // Apply pagination
    var paged = filtered
        .Skip((int)request.PageIndex * (int)request.PageSize)
        .Take((int)request.PageSize);

    // Map and return
    return new QueryResult<ProductDto>(
        paged.To<ProductDto>(),
        totalCount,
        request.PageIndex,
        request.PageSize
    );
}

Client-Side Pagination

Clients can request specific pages:
var query = new GetAllDummyEntitiesQuery
{
    PageIndex = 0,  // First page
    PageSize = 20   // 20 items per page
};

var result = await _commandQueryBus.Send(query);

// Access results
var items = result.Items;           // Current page items
var totalCount = result.Count;       // Total items across all pages
var totalPages = (int)Math.Ceiling((double)result.Count / result.PageSize);

Best Practices

  • Use Get prefix: GetAllEntities, GetEntityById
  • Be specific about what’s returned: GetActiveUsers, GetRecentOrders
  • Append Query suffix for clarity
  • Avoid generic names like GetData or Fetch
  • Queries should NEVER modify state
  • Use read-only repositories when available
  • Consider using IReadRepository<T> for explicit read-only access
  • Never call Add, Update, or Remove in query handlers
  • Always return DTOs, never domain entities
  • Use the To<TDto>() extension method for mapping
  • DTOs should be optimized for the query use case
  • Include only necessary data to minimize payload
  • Use async methods (FindAllAsync, FindOneAsync)
  • Implement pagination for large datasets
  • Consider caching for frequently accessed queries
  • Project to DTOs at the database level when possible

Query vs Command

AspectQueryCommand
PurposeRead dataModify data
Return ValueDTOs with dataID or void
Side EffectsNonePersists changes
Repository MethodsFindAll, FindOneAdd, Update, Remove
EventsNever publishesPublishes domain events
ValidationParameter validation onlyFull business rule validation

Build docs developers (and LLMs) love