Skip to main content

Overview

The Intent.Application.MediatR.CRUD module automatically implements common CRUD (Create, Read, Update, Delete) operations in your command and query handlers. Instead of manually writing repository code, this module detects CRUD patterns and generates the implementation for you.
This module works by analyzing the names and mappings of your commands/queries to determine their intent (e.g., “CreateCustomer” → Create operation).

Supported CRUD Operations

The module recognizes and implements these operations:

Create

Commands: Create*, Add*, New*Generates code to instantiate and add entities to the repository.

Read

Queries: Get*, Find*, Lookup*Generates code to retrieve entities by ID or other criteria.

Update

Commands: Update*, Modify*, Edit*Generates code to find and update existing entities.

Delete

Commands: Delete*, Remove*Generates code to find and remove entities.

How It Works

1. Name-Based Detection

The module examines your command/query names:
CreateCustomerCommand → Create strategy
GetCustomerByIdQuery → GetById strategy  
UpdateCustomerCommand → Update strategy
DeleteCustomerCommand → Delete strategy

2. Mapping Detection

It also checks if your command/query is mapped to a domain entity in the designer:
  • Command mapped to Customer entity → CRUD operation likely
  • Return type is a DTO mapped to the same entity → Mapping included

3. Code Generation

When both conditions are met, the module generates the full handler implementation.

Generated Create Implementation

Given a CreateCustomerCommand mapped to a Customer entity:
CreateCustomerCommandHandler.cs
[IntentManaged(Mode.Merge, Signature = Mode.Fully)]
public class CreateCustomerCommandHandler : IRequestHandler<CreateCustomerCommand, Guid>
{
    private readonly ICustomerRepository _customerRepository;
    private readonly IMapper _mapper;

    [IntentManaged(Mode.Merge)]
    public CreateCustomerCommandHandler(ICustomerRepository customerRepository, IMapper mapper)
    {
        _customerRepository = customerRepository;
        _mapper = mapper;
    }

    [IntentManaged(Mode.Fully, Body = Mode.Fully)]
    public async Task<Guid> Handle(CreateCustomerCommand request, CancellationToken cancellationToken)
    {
        var customer = new Customer
        {
            Name = request.Name,
            Surname = request.Surname,
            Email = request.Email
        };

        _customerRepository.Add(customer);
        await _customerRepository.UnitOfWork.SaveChangesAsync(cancellationToken);
        return customer.Id;
    }
}
Key features:
  • Automatically injects required repository
  • Maps command properties to entity properties
  • Handles nested composite entities
  • Returns entity ID or mapped DTO

Generated GetById Implementation

Given a GetCustomerByIdQuery mapped to a Customer entity:
GetCustomerByIdQueryHandler.cs
[IntentManaged(Mode.Merge, Signature = Mode.Fully)]
public class GetCustomerByIdQueryHandler : IRequestHandler<GetCustomerByIdQuery, CustomerDto>
{
    private readonly ICustomerRepository _customerRepository;
    private readonly IMapper _mapper;

    [IntentManaged(Mode.Merge)]
    public GetCustomerByIdQueryHandler(ICustomerRepository customerRepository, IMapper mapper)
    {
        _customerRepository = customerRepository;
        _mapper = mapper;
    }

    [IntentManaged(Mode.Fully, Body = Mode.Fully)]
    public async Task<CustomerDto> Handle(GetCustomerByIdQuery request, CancellationToken cancellationToken)
    {
        var customer = await _customerRepository.FindByIdAsync(request.Id, cancellationToken);
        if (customer is null)
        {
            throw new NotFoundException($"Could not find Customer '{request.Id}'");
        }

        return customer.MapToCustomerDto(_mapper);
    }
}
Key features:
  • Finds entity by ID from query parameters
  • Throws NotFoundException if not found (or returns null if nullable)
  • Maps entity to DTO using AutoMapper or Mapperly

Generated Update Implementation

Given an UpdateCustomerCommand mapped to a Customer entity:
UpdateCustomerCommandHandler.cs
[IntentManaged(Mode.Merge, Signature = Mode.Fully)]
public class UpdateCustomerCommandHandler : IRequestHandler<UpdateCustomerCommand>
{
    private readonly ICustomerRepository _customerRepository;

    [IntentManaged(Mode.Merge)]
    public UpdateCustomerCommandHandler(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
    }

    [IntentManaged(Mode.Fully, Body = Mode.Fully)]
    public async Task Handle(UpdateCustomerCommand request, CancellationToken cancellationToken)
    {
        var customer = await _customerRepository.FindByIdAsync(request.Id, cancellationToken);
        if (customer is null)
        {
            throw new NotFoundException($"Could not find Customer '{request.Id}'");
        }

        customer.Name = request.Name;
        customer.Surname = request.Surname;
        customer.Email = request.Email;

        await _customerRepository.UnitOfWork.SaveChangesAsync(cancellationToken);
    }
}
Key features:
  • Finds existing entity
  • Updates properties from command
  • Handles domain operations and value objects

Generated Delete Implementation

Given a DeleteCustomerCommand with an Id property:
DeleteCustomerCommandHandler.cs
[IntentManaged(Mode.Merge, Signature = Mode.Fully)]
public class DeleteCustomerCommandHandler : IRequestHandler<DeleteCustomerCommand>
{
    private readonly ICustomerRepository _customerRepository;

    [IntentManaged(Mode.Merge)]
    public DeleteCustomerCommandHandler(ICustomerRepository customerRepository)
    {
        _customerRepository = customerRepository;
    }

    [IntentManaged(Mode.Fully, Body = Mode.Fully)]
    public async Task Handle(DeleteCustomerCommand request, CancellationToken cancellationToken)
    {
        var customer = await _customerRepository.FindByIdAsync(request.Id, cancellationToken);
        if (customer is null)
        {
            throw new NotFoundException($"Could not find Customer '{request.Id}'");
        }

        _customerRepository.Remove(customer);
        await _customerRepository.UnitOfWork.SaveChangesAsync(cancellationToken);
    }
}
Key features:
  • Finds entity before deletion
  • Removes from repository
  • Throws exception if not found

Advanced Features

Nested Composite Entities

For entities with compositional relationships:
CreateOrderLineCommand
// OrderLine is owned by Order (composite relationship)
public async Task<Guid> Handle(CreateOrderLineCommand request, CancellationToken cancellationToken)
{
    var aggregateRoot = await _orderRepository.FindByIdAsync(request.OrderId, cancellationToken);
    if (aggregateRoot is null)
    {
        throw new NotFoundException($"Order of Id '{request.OrderId}' could not be found");
    }

    var orderLine = new OrderLine
    {
        ProductId = request.ProductId,
        Quantity = request.Quantity
    };

    aggregateRoot.OrderLines.Add(orderLine);
    _orderRepository.Update(aggregateRoot);
    await _orderRepository.UnitOfWork.SaveChangesAsync(cancellationToken);
    return orderLine.Id;
}
Features:
  • Loads aggregate root
  • Adds child entity to collection
  • Updates aggregate root

Value Object Mapping

For properties mapped to value objects:
var customer = new Customer
{
    Name = request.Name,
    Address = CreateAddress(request.Address), // Value object factory
    Email = request.Email
};

private static Address CreateAddress(AddressDto dto)
{
    return new Address(
        street: dto.Street,
        city: dto.City,
        postalCode: dto.PostalCode
    );
}
Features:
  • Generates factory methods for value objects
  • Handles both single and collection mappings

Pagination Support

For GetAll queries with pagination:
GetCustomersQuery
public async Task<PagedResult<CustomerDto>> Handle(
    GetCustomersQuery request, 
    CancellationToken cancellationToken)
{
    var customers = await _customerRepository
        .FindAllAsync(request.PageNo, request.PageSize, cancellationToken);
    
    return customers.MapToPagedResult(x => x.MapToCustomerDto(_mapper));
}

Requirements for CRUD Generation

For the module to generate CRUD implementations, you need:
  1. Naming Convention: Command/query name matches a CRUD pattern
  2. Domain Mapping: Command/query is mapped to a domain entity
  3. DTO Mapping: Return type (for queries) is a DTO mapped to the same entity
  4. Repository Available: A repository interface exists for the entity
  5. Entity Settings: Domain entity has Ensure Private Property Setters disabled
If Ensure Private Property Setters is enabled in Domain Settings, CRUD generation is disabled because the module cannot set properties directly.

When CRUD Is NOT Generated

The module will not generate CRUD code when:
  • Command/query name doesn’t match a CRUD pattern
  • No mapping to domain entity exists
  • Ensure Private Property Setters is enabled
  • Repository interface is missing
  • You’ve added custom Domain Interactions in the designer
In these cases, you’ll get the default handler stub:
public async Task Handle(MyCommand request, CancellationToken cancellationToken)
{
    // IntentInitialGen
    // TODO: Implement Handle (MyCommandHandler) functionality
    throw new NotImplementedException("Your implementation here...");
}

Dependencies

This module requires:
  • Intent.Application.MediatR - Base MediatR implementation
  • Intent.Application.DomainInteractions - Domain interaction patterns
  • Intent.Common.Types - Common type definitions
  • Intent.Modelers.Domain - Domain entity modeling
Optional dependencies:
  • Intent.Application.AutoMapper or Intent.Application.Dtos.Mapperly - For DTO mapping

MediatR

Core CQRS implementation

Domain Interactions

Manual domain interaction patterns

Repositories

Repository pattern implementation

Troubleshooting

CRUD Code Not Generating?

Check these:
  1. Command/query name matches CRUD pattern (Create*, Get*, Update*, Delete*)
  2. Mapping exists in the Services Designer to a domain entity
  3. Domain SettingsEnsure Private Property Setters is disabled
  4. Repository interface exists for the entity
  5. Return type (for queries) is a DTO mapped to the same entity

Warning Comments in Generated Code

You may see warnings like:
#warning No matching field found for PropertyName
#warning Field not a composite association: PropertyName
These indicate:
  • Missing mappings between DTO and entity properties
  • Association type mismatch (aggregation vs. composition)
  • Value object mapping issues
Review your mappings in the Services Designer to resolve these.

Additional Resources

Build docs developers (and LLMs) love