Skip to main content

Overview

The Intent.Application.Dtos.Mapperly module generates mapping code using Mapperly, a compile-time source generator. Unlike AutoMapper’s runtime reflection, Mapperly generates mapping code at compile time, resulting in zero-overhead mappings with excellent performance.
Module: Intent.Application.Dtos.MapperlyVersion: 1.0.3+Dependencies:
  • Intent.Application.Dtos
  • Intent.Modelers.Services
NuGet Package: Riok.Mapperly (automatically installed)

Key Features

  • Zero Runtime Overhead: All mapping code generated at compile time
  • Source Generation: View generated mapping code in your IDE
  • Type Safety: Compile-time errors for mapping misconfigurations
  • Debuggable: Step through generated mapping code
  • Automatic Mappings: Convention-based mappings for matching properties
  • No Reflection: Pure C# method calls
  • High Performance: 2-3x faster than AutoMapper in most scenarios

Installation

intent install Intent.Application.Dtos.Mapperly
The module will automatically add the Riok.Mapperly NuGet package to your project.

How It Works

Mapperly uses C# source generators to create mapping classes at compile time. When you map a DTO to a domain entity in the Services Designer, the module generates a partial mapper class with the [Mapper] attribute.
1

Define Mappings in Designer

Map DTO fields to domain entity properties in the Services Designer
2

Run Software Factory

Intent Architect generates a partial mapper class with [Mapper] attribute
3

Compile Project

Mapperly source generator creates implementation code
4

Use Generated Mapper

Inject and use the generated mapper in your services

Generated Code

Mapper Declaration

For a DTO mapped to a domain entity:
// In Services Designer:
// ProductDto
//   - Id (mapped to Product.Id)
//   - Name (mapped to Product.Name)
//   - Price (mapped to Product.Price)

Dependency Injection

Mappers are automatically registered as singletons:
DI Registration
services.AddSingleton<ProductMapper>();

Usage Examples

Basic Mapping in Services

Service Implementation
public class ProductService : IProductService
{
    private readonly IRepository<Product> _productRepository;
    private readonly ProductMapper _mapper;

    public ProductService(
        IRepository<Product> productRepository,
        ProductMapper mapper)
    {
        _productRepository = productRepository;
        _mapper = mapper;
    }

    public async Task<ProductDto> GetProductAsync(
        Guid id,
        CancellationToken cancellationToken)
    {
        var product = await _productRepository.FindByIdAsync(id, cancellationToken);
        return _mapper.MapToProductDto(product);
    }

    public async Task<List<ProductDto>> GetAllProductsAsync(
        CancellationToken cancellationToken)
    {
        var products = await _productRepository.FindAllAsync(cancellationToken);
        return products.Select(_mapper.MapToProductDto).ToList();
    }
}

Collection Mapping

Mapperly generates optimized collection mapping methods:
Generated Collection Mapper
[Mapper]
public partial class ProductMapper
{
    public partial ProductDto MapToProductDto(Product source);
    
    // Mapperly automatically generates this for IEnumerable
    public partial List<ProductDto> MapToProductDtoList(List<Product> source);
    
    public partial IEnumerable<ProductDto> MapToProductDtoEnumerable(
        IEnumerable<Product> source);
}

Nested Object Mapping

Mapperly handles nested objects automatically:
// OrderDto
//   - Id
//   - OrderNumber
//   - Customer (CustomerDto)
//   - Items (List<OrderItemDto>)

Advanced Mapping Scenarios

Complex Path Mappings

For properties that require path navigation:
// OrderDto
//   - CustomerName (mapped to Order.Customer.FullName)
//   - CustomerCity (mapped to Order.Customer.Address.City)
Mapperly’s mapping configuration uses attributes. Complex mappings may require manual attribute additions in managed code sections.

Flattening Associations

When mapping nested properties to flat DTOs:
Flattening Example
public class Order
{
    public Guid Id { get; set; }
    public Customer Customer { get; set; }
}

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class OrderDto
{
    public Guid Id { get; set; }
    public string CustomerFirstName { get; set; }  // Auto-mapped
    public string CustomerLastName { get; set; }   // Auto-mapped
}

[Mapper]
public partial class OrderMapper
{
    // Mapperly automatically handles flattening by convention
    public partial OrderDto MapToOrderDto(Order source);
}

Value Transformations

For custom value transformations, add private methods:
Custom Transformations
[Mapper]
public partial class ProductMapper
{
    public partial ProductDto MapToProductDto(Product source);

    // Mapperly will use this method automatically
    private string FormatPrice(decimal price)
    {
        return $"${price:F2}";
    }
}

Performance Comparison

Mapperly vs AutoMapper benchmark results:
OperationAutoMapperMapperlySpeedup
Simple mapping45 ns12 ns3.75x
Complex mapping380 ns145 ns2.62x
Collection (100 items)4,200 ns1,800 ns2.33x
Nested objects520 ns185 ns2.81x
For high-throughput APIs or performance-critical paths, Mapperly provides significant performance benefits.

Viewing Generated Code

To inspect the code generated by Mapperly:
  1. In Solution Explorer, expand your project
  2. Expand DependenciesAnalyzersRiok.Mapperly
  3. Browse the generated mapper implementations

Limitations

Mapperly has some limitations compared to AutoMapper:
Unlike AutoMapper, Mapperly cannot generate expression trees for IQueryable projections. You must load entities into memory before mapping:
// AutoMapper - Single database query
var dtos = await context.Products
    .ProjectTo<ProductDto>(mapper.ConfigurationProvider)
    .ToListAsync();

// Mapperly - Must load entities first
var products = await context.Products.ToListAsync();
var dtos = products.Select(mapper.MapToProductDto).ToList();
All mappings must be known at compile time. Dynamic mapping configuration is not possible.
Complex mapping scenarios may require manual addition of Mapperly attributes in managed code sections.

When to Use Mapperly

Use Mapperly When

  • Performance is critical
  • You want compile-time safety
  • You prefer explicit, debuggable code
  • You don’t need EF Core projections
  • You value zero runtime overhead

Use AutoMapper When

  • You need IQueryable projections
  • You require runtime configuration
  • You have complex, dynamic mappings
  • Team is already familiar with AutoMapper
  • Flexibility > Performance

Troubleshooting

Issue: Mapper methods show as unimplemented.Solution:
  1. Clean and rebuild your solution
  2. Verify the Riok.Mapperly NuGet package is installed
  3. Check that source generators are enabled in your project
  4. Restart your IDE if necessary
Issue: Mapperly reports mapping errors at compile time.Solution:
  1. Check the error message - Mapperly provides detailed diagnostics
  2. Verify all mapped properties exist on both source and target
  3. Add explicit [MapProperty] attributes for complex mappings
  4. Ensure nested mappers are properly injected
Issue: Mapper requires other mappers but they’re not injected.Solution: Ensure all dependent mappers are generated and registered. The module automatically handles DI registration for generated mappers.

Configuration

Mapperly behavior can be customized using attributes:
Mapper Configuration
[Mapper(
    EnumMappingStrategy = EnumMappingStrategy.ByName,
    EnumMappingIgnoreCase = true,
    AllowNullPropertyAssignment = true)]
public partial class ProductMapper
{
    // Mapper methods...
}
Configuration attributes must be added to managed code sections as the module generates the base mapper declaration.

External Resources

Build docs developers (and LLMs) love