Skip to main content

Overview

The Intent.Application.Dtos.AutoMapper module generates AutoMapper mapping profiles for DTOs that map to/from domain entities. This eliminates the need to manually write repetitive mapping code while maintaining type safety and performance.
Module: Intent.Application.Dtos.AutoMapperVersion: 4.0.19+Dependencies:
  • Intent.Application.Dtos
  • Intent.Application.AutoMapper
  • Intent.Modelers.Services

Key Features

  • Automatic Profile Generation: Creates AutoMapper profiles for domain-to-DTO mappings
  • Bi-Directional Mapping: Supports mapping in both directions
  • Complex Path Mapping: Handles nested property paths and associations
  • Mapping Extensions: Generates convenient extension methods
  • Expression-Based Projections: Supports EF Core IQueryable projections
  • Advanced Mapping Support: Custom mappings, conditional logic, and transformations
  • Nullable Association Handling: Safe traversal of nullable relationships

Installation

intent install Intent.Application.Dtos.AutoMapper
This will also install the required Intent.Application.AutoMapper module if not already present.

How It Works

When you map a DTO field to a domain entity attribute in the Services Designer, the module:
  1. Generates an AutoMapper Profile class
  2. Creates bidirectional mappings between entity and DTO
  3. Adds member-specific mapping configurations for complex paths
  4. Generates mapping extension methods for convenience

Generated Code

Mapping Profile

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

Mapping Extensions

The module also generates convenient extension methods:
Generated Extensions
using AutoMapper;
using MyApp.Application.Products;
using MyApp.Domain.Entities;

[assembly: DefaultIntentManaged(Mode.Fully)]

namespace MyApp.Application.Mappings
{
    public static class ProductDtoMappingExtensions
    {
        public static ProductDto MapToProductDto(this Product projectFrom, IMapper mapper)
            => mapper.Map<ProductDto>(projectFrom);

        public static List<ProductDto> MapToProductDtoList(this IEnumerable<Product> projectFrom, IMapper mapper)
            => projectFrom.Select(x => x.MapToProductDto(mapper)).ToList();
    }
}

Usage Examples

Basic Mapping in Services

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

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

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

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

EF Core Projections

AutoMapper profiles work seamlessly with EF Core for efficient database queries:
Efficient Query Projection
public async Task<List<ProductDto>> SearchProductsAsync(
    string searchTerm,
    CancellationToken cancellationToken)
{
    var products = await _productRepository
        .Query()
        .Where(p => p.Name.Contains(searchTerm))
        .ProjectTo<ProductDto>(_mapper.ConfigurationProvider)
        .ToListAsync(cancellationToken);

    return products;
}
Using ProjectTo<T>() is more efficient than Select().ToList() followed by Map() because AutoMapper generates the SQL projection directly.

Reverse Mapping

DTO to Entity
public async Task<Guid> CreateProductAsync(
    ProductDto productDto,
    CancellationToken cancellationToken)
{
    var product = _mapper.Map<Product>(productDto);
    await _productRepository.AddAsync(product, cancellationToken);
    return product.Id;
}

Advanced Mapping Scenarios

Complex Path Mappings

When mapping across multiple associations:
// OrderDto
//   - CustomerName (mapped to Order.Customer.FullName)
//   - CustomerCity (mapped to Order.Customer.Address.City)
//   - TotalItems (mapped to Order.Items.Count)
The null-forgiving operator (!) is added for nullable associations. While this prevents analyzer warnings, ensure your domain logic never creates orphaned entities.

Collection Mappings

AutoMapper automatically handles collection mappings:
Collection Mapping
// OrderDto has a collection of OrderItemDto
// Each OrderItemDto maps to OrderItem entity

CreateMap<Order, OrderDto>()
    .ForMember(d => d.Items, opt => opt.MapFrom(src => src.OrderItems));

CreateMap<OrderItem, OrderItemDto>();

Surrogate Key Mapping

Map association entities to their IDs:
// ProductDto
//   - CategoryId (mapped to Product.Category entity)

Configuration

Profile Location

Control where AutoMapper profiles are generated:
Application Settings > AutoMapper > Profile Location
Default: Mappings/

Custom Mapping Configuration

For custom mapping logic not supported by the designer, use decorators:
Custom Mapping Decorator
using Intent.RoslynWeaver.Attributes;

[assembly: DefaultIntentManaged(Mode.Merge)]

namespace MyApp.Application.Mappings
{
    public partial class ProductDtoMappingProfile
    {
        partial void ConfigureMappingProfile()
        {
            CreateMap<Product, ProductDto>()
                .ForMember(d => d.DisplayName, 
                    opt => opt.MapFrom(src => $"{src.Name} - {src.Sku}"))
                .ForMember(d => d.IsExpensive, 
                    opt => opt.MapFrom(src => src.Price > 1000m));
        }
    }
}

Performance Considerations

Always use ProjectTo<T>() when working with EF Core IQueryable to generate optimized SQL queries:
// ✅ Good - Single efficient query
var dtos = await context.Products
    .ProjectTo<ProductDto>(mapper.ConfigurationProvider)
    .ToListAsync();

// ❌ Bad - Multiple queries and unnecessary data loading
var products = await context.Products.ToListAsync();
var dtos = mapper.Map<List<ProductDto>>(products);
Don’t map entire object graphs if you only need a few properties. Create specialized DTOs for specific use cases.
The IMapper instance is registered as a singleton by the Intent.Application.AutoMapper module, so configuration is cached automatically.

Troubleshooting

Issue: AutoMapperMappingException: Missing type map configurationSolution:
  1. Ensure the DTO field is mapped in the Services Designer
  2. Run the Software Factory to regenerate profiles
  3. Verify the target entity/DTO exists and has the correct template role
Issue: Null reference exceptions when mapping nullable associations.Solution: While the generated code includes null-forgiving operators, ensure your domain entities maintain referential integrity or handle nulls explicitly in custom configurations.
Issue: Compilation errors due to namespace conflicts.Solution: The module automatically adds necessary using directives. If issues persist, check for naming conflicts between your DTOs and entities.

Comparison with Mapperly

FeatureAutoMapperMapperly
PerformanceRuntime reflectionCompile-time source generation
SetupConfiguration via profilesAttribute-based
FlexibilityHighly configurableMore limited
DebuggingHarder to debugEasy to inspect generated code
EF ProjectionsExcellent supportNot supported
Learning CurveModerateEasier
For most applications, AutoMapper is recommended due to its flexibility and excellent EF Core integration. Consider Mapperly for performance-critical scenarios where projections aren’t needed.

External Resources

Build docs developers (and LLMs) love