Skip to content

Migration Guide

davidkallesen edited this page Mar 12, 2026 · 1 revision

πŸ”„ Migration Guide - Object Mapping Generator

Migrate from AutoMapper, Mapperly, or Mapster to the Atc Mapping Generator. This guide covers concept mapping, step-by-step migration, and strategies for gradual adoption.

🧭 Documentation Navigation

Table of Contents


πŸ€” Why Migrate to Atc Mapping Generator

🎯 Key Advantages

  • πŸš€ Native AOT compatible - Zero reflection at runtime. All mapping code is generated at compile time, making it fully compatible with Native AOT and trimming scenarios.
  • ⚑ Zero runtime cost - Generated code is pure C# switch expressions, property assignments, and constructor calls. No hidden allocations, no delegate invocations, no dictionary lookups.
  • πŸ”€ Both attribute-based AND configuration-based approaches - Use [MapTo] attributes on your own types, or use configuration-based mapping for 3rd-party types you cannot modify.
  • πŸ’‘ Full IntelliSense support - Generated extension methods appear in IntelliSense with proper documentation. Navigate to generated code with F12.
  • πŸ”„ Enum auto-detection with special case handling - Automatically maps None to Unknown, Active to Enabled, and other common patterns without manual configuration.
  • πŸ“¦ Works with 3rd-party types without modifying them - Configuration-based mapping lets you define mappings for types you do not own (EF Core entities, NuGet package models, Protobuf messages).

πŸ“Š Comparison at a Glance

Feature AutoMapper Mapperly Mapster Atc Mapping
Reflection-free ❌ βœ… ❌ βœ…
Native AOT ❌ βœ… ❌ βœ…
Compile-time generation ❌ βœ… ❌ βœ…
Attribute-based API ❌ Partial ❌ βœ…
Configuration-based API βœ… βœ… βœ… βœ…
3rd-party type mapping βœ… βœ… βœ… βœ…
Enum special cases ❌ Manual ❌ Manual ❌ Manual βœ… Auto
Constructor mapping βœ… βœ… βœ… βœ…
Collection mapping βœ… βœ… βœ… βœ…
Bidirectional mapping βœ… Manual βœ… βœ… βœ… Bidirectional = true
Base class inheritance βœ… βœ… βœ… βœ…
Property flattening βœ… βœ… βœ… βœ…
Naming strategy βœ… ❌ βœ… βœ…

πŸ”§ Migration from AutoMapper

AutoMapper uses runtime reflection and a fluent configuration API. Migrating to Atc Mapping Generator replaces runtime mapping with compile-time generated code.

πŸ“‹ Concept Mapping Table

AutoMapper Concept Atc Mapping Equivalent Notes
Profile MappingConfiguration partial class Configuration-based approach for 3rd-party types
CreateMap<TSource, TDest>() [MapTo(typeof(TDest))] on source class Attribute-based: decorate the source type
ForMember(d => d.X, o => o.MapFrom(s => s.Y)) [MapProperty("X")] on source property Renames the property in the target
ForMember(d => d.X, o => o.Ignore()) [MapIgnore] on source or target property Excludes property from mapping
ReverseMap() Bidirectional = true on [MapTo] Generates both forward and reverse mappings
IMapper.Map<TDest>(source) source.MapToTDest() Extension method, no DI required
IncludeBase<TBase, TBaseDest>() Automatic via inheritance Base class properties included automatically
ConvertUsing(converter) Built-in type conversions Common conversions handled automatically
AddMaps(assembly) Not needed All mappings discovered at compile time
AssertConfigurationIsValid() Compile-time diagnostics Errors reported as build warnings/errors

πŸ“ Step-by-Step Guide

Step 1: πŸ—‘οΈ Remove AutoMapper packages

dotnet remove package AutoMapper
dotnet remove package AutoMapper.Extensions.Microsoft.DependencyInjection

Step 2: πŸ“¦ Add Atc Source Generators

dotnet add package Atc.SourceGenerators
dotnet add package Atc.SourceGenerators.Annotations

Step 3: 🏷️ Replace Profile classes with attributes

Before (AutoMapper):

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<User, UserDto>()
            .ForMember(d => d.FullName, o => o.MapFrom(s => s.Name))
            .ForMember(d => d.InternalId, o => o.Ignore());

        CreateMap<User, UserDto>().ReverseMap();
    }
}

After (Atc Mapping Generator):

[MapTo(typeof(UserDto), Bidirectional = true)]
public partial class User
{
    public Guid Id { get; set; }

    [MapProperty("FullName")]
    public string Name { get; set; } = string.Empty;

    [MapIgnore]
    public string InternalId { get; set; } = string.Empty;
}

Step 4: πŸ”„ Replace IMapper calls with extension methods

Before (AutoMapper):

public class UserService
{
    private readonly IMapper _mapper;

    public UserService(IMapper mapper) => _mapper = mapper;

    public UserDto GetUser(User user) => _mapper.Map<UserDto>(user);
}

After (Atc Mapping Generator):

using Atc.Mapping;

public class UserService
{
    // No DI dependency needed
    public UserDto GetUser(User user) => user.MapToUserDto();
}

Step 5: 🧹 Remove DI registration

Before (AutoMapper):

services.AddAutoMapper(typeof(Program).Assembly);

After (Atc Mapping Generator):

// No registration needed - mappings are extension methods

Step 6: πŸ”¨ Build and fix any diagnostics

dotnet build

The compiler will report ATCMAP001 (class must be partial), ATCMAP002 (invalid target type), or ATCMAP003 (property not found) diagnostics for any issues.

βœ… Feature Parity Table

AutoMapper Feature Atc Mapping Support Notes
Property-to-property mapping βœ… Automatic by name
Constructor mapping βœ… Records and primary constructors
Collection mapping βœ… List, Array, IEnumerable, etc.
Nested object mapping βœ… Automatic chaining
Null substitution ❌ Use default values on properties
Value converters βœ… Built-in type conversions
Conditional mapping ❌ Apply conditions in calling code
BeforeMap / AfterMap ❌ Use wrapper methods
Projection (ProjectTo) ❌ Use manual LINQ projections
Flattening (Order.Customer.Name -> CustomerName) βœ… Property flattening support
Polymorphic mapping βœ… Derived type mapping
Enum mapping βœ… Auto-detection with special cases
Bidirectional mapping βœ… Bidirectional = true
Naming conventions βœ… PropertyNameStrategy
Ignore properties βœ… [MapIgnore] attribute
Custom property names βœ… [MapProperty("Name")]

πŸ”§ Migration from Mapperly

Mapperly is also a source generator, so the migration is more straightforward. The key difference is the API style: Mapperly uses partial mapper classes, while Atc uses attributes on source types or configuration classes.

πŸ“‹ Concept Mapping Table

Mapperly Concept Atc Mapping Equivalent Notes
[Mapper] partial class [MapTo] attribute on source type Attribute-based: no separate mapper class needed
partial UserDto MapToDto(User user) Auto-generated user.MapToUserDto() Extension method generated automatically
[MapperIgnoreTarget("Prop")] [MapIgnore] on target property Or on source property
[MapperIgnoreSource("Prop")] [MapIgnore] on source property Same attribute for both directions
[MapProperty("Source", "Target")] [MapProperty("TargetName")] on source property Placed directly on the source property
[MapEnum(EnumMappingStrategy.ByName)] Automatic (always by name) With special case detection
[MapperRequiredMapping] Compile-time diagnostics ATCMAP003 for missing properties
[ObjectFactory] Constructor detection Automatic constructor selection
Mapper DI injection Not needed Extension methods, no DI required

πŸ” Key Differences

1. ✨ No separate mapper class needed

Mapperly requires a dedicated partial mapper class:

// Mapperly
[Mapper]
public partial class UserMapper
{
    public partial UserDto MapToDto(User user);
    public partial User MapFromDto(UserDto dto);
}

Atc Mapping Generator decorates the source type directly:

// Atc Mapping Generator
[MapTo(typeof(UserDto), Bidirectional = true)]
public partial class User
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

2. πŸ”— Extension methods vs instance methods

Mapperly generates instance methods on the mapper class. Atc generates extension methods in the Atc.Mapping namespace:

// Mapperly
var mapper = new UserMapper();
var dto = mapper.MapToDto(user);

// Atc Mapping Generator
using Atc.Mapping;
var dto = user.MapToUserDto();

3. πŸ”„ Enum mapping is automatic

Mapperly requires explicit enum mapping strategy configuration. Atc automatically maps enums by name with special case detection (e.g., None maps to Unknown):

// Mapperly - requires explicit configuration
[Mapper]
public partial class StatusMapper
{
    [MapEnum(EnumMappingStrategy.ByName)]
    public partial StatusDto MapToDto(Status status);
}

// Atc Mapping Generator - automatic
[MapTo(typeof(StatusDto))]
public enum Status { None, Active, Inactive }
// None -> Unknown mapped automatically if target has Unknown

4. πŸ“¦ 3rd-party type mapping

Both support mapping types you do not own, but with different approaches:

// Mapperly - partial method on mapper class
[Mapper]
public partial class ExternalMapper
{
    public partial MyDto MapToDto(ExternalLib.Model source);
}

// Atc Mapping Generator - configuration-based approach
// See the Configuration-Based Mapping wiki page for details

πŸ”§ Migration from Mapster

Mapster uses runtime reflection with a fluent configuration API, similar to AutoMapper but with a different syntax. Migration follows a similar pattern.

πŸ“‹ Concept Mapping Table

Mapster Concept Atc Mapping Equivalent Notes
TypeAdapterConfig [MapTo] attribute or MappingConfiguration Attribute-based or configuration-based
source.Adapt<TDest>() source.MapToTDest() Strongly-typed extension method
TypeAdapterConfig.NewConfig<S, D>() [MapTo(typeof(D))] on source Compile-time configuration
.Map("TargetProp", "SourceProp") [MapProperty("TargetProp")] on source property Direct property annotation
.Ignore("Prop") [MapIgnore] on property Excludes from mapping
.TwoWays() Bidirectional = true Generates both directions
.ConstructUsing(() => new T()) Automatic constructor detection Prefers constructors with matching parameters
.MapWith(expr) Built-in type conversions Common conversions handled automatically
.AddDestinationTransform(...) Not needed Use property defaults or post-processing
TypeAdapterConfig.GlobalSettings Not applicable Each mapping is self-contained

πŸ“ Step-by-Step Guide

Step 1: πŸ—‘οΈ Remove Mapster packages

dotnet remove package Mapster
dotnet remove package Mapster.DependencyInjection

Step 2: πŸ“¦ Add Atc Source Generators

dotnet add package Atc.SourceGenerators
dotnet add package Atc.SourceGenerators.Annotations

Step 3: 🏷️ Replace TypeAdapterConfig with attributes

Before (Mapster):

// In Startup or configuration
TypeAdapterConfig<Order, OrderDto>.NewConfig()
    .Map(dest => dest.CustomerName, src => src.Customer.Name)
    .Ignore(dest => dest.InternalNotes)
    .TwoWays();

After (Atc Mapping Generator):

[MapTo(typeof(OrderDto), Bidirectional = true)]
public partial class Order
{
    public Guid Id { get; set; }

    // Customer.Name -> CustomerName handled by property flattening
    public Customer Customer { get; set; } = null!;

    [MapIgnore]
    public string InternalNotes { get; set; } = string.Empty;
}

Step 4: πŸ”„ Replace Adapt<T>() calls with extension methods

Before (Mapster):

var dto = order.Adapt<OrderDto>();
var orders = orderList.Select(o => o.Adapt<OrderDto>()).ToList();

After (Atc Mapping Generator):

using Atc.Mapping;

var dto = order.MapToOrderDto();
var orders = orderList.Select(o => o.MapToOrderDto()).ToList();

Step 5: 🧹 Replace DI-based mapping

Before (Mapster):

services.AddMapster();

// In service class
public class OrderService
{
    private readonly IMapper _mapper;
    public OrderService(IMapper mapper) => _mapper = mapper;
    public OrderDto Get(Order order) => _mapper.Map<OrderDto>(order);
}

After (Atc Mapping Generator):

// No DI registration needed

public class OrderService
{
    // No mapper dependency
    public OrderDto Get(Order order) => order.MapToOrderDto();
}

Step 6: πŸ”¨ Build and resolve diagnostics

dotnet build

Review any ATCMAP001, ATCMAP002, or ATCMAP003 diagnostics and fix accordingly.


🚢 Hybrid / Gradual Migration Approach

You do not need to migrate everything at once. The Atc Mapping Generator can coexist with AutoMapper, Mapperly, or Mapster during a gradual migration.

πŸ“‹ Strategy Overview

Phase Action Scope
πŸ“¦ Phase 1 Add Atc packages alongside existing mapper No existing code changes
✨ Phase 2 Migrate new code to Atc mappings New features only
πŸ—οΈ Phase 3 Migrate layer by layer One project at a time
πŸ—‘οΈ Phase 4 Remove old mapper package After full migration

πŸ“¦ Phase 1: Add Packages Side-by-Side

Install Atc packages without removing the existing mapper:

dotnet add package Atc.SourceGenerators
dotnet add package Atc.SourceGenerators.Annotations

Both mapping systems can coexist. AutoMapper/Mapster use IMapper via DI, while Atc generates extension methods in the Atc.Mapping namespace. There are no conflicts.

✨ Phase 2: Use Atc for New Code

For any new types, use [MapTo] attributes instead of adding new Profile/Config entries:

// New feature - use Atc Mapping Generator
[MapTo(typeof(InvoiceDto), Bidirectional = true)]
public partial class Invoice
{
    public Guid Id { get; set; }
    public decimal Amount { get; set; }
    public InvoiceStatus Status { get; set; }
}

// Existing code continues using AutoMapper/Mapster
var oldDto = _mapper.Map<OrderDto>(order);

// New code uses generated extension methods
var newDto = invoice.MapToInvoiceDto();

πŸ—οΈ Phase 3: Migrate Layer by Layer

Migrate one project at a time, starting from the innermost layer (typically DataAccess or Domain):

Step 1: Migrate DataAccess entities    (lowest dependency count)
Step 2: Migrate Domain models          (depends on DataAccess)
Step 3: Migrate API DTOs               (depends on Domain)
Step 4: Remove mapper from DI          (after all layers migrated)

For each type being migrated:

  1. ✏️ Add partial keyword to the class declaration
  2. 🏷️ Add [MapTo(typeof(Target))] attribute
  3. πŸ”§ Add [MapIgnore] and [MapProperty] as needed
  4. πŸ”„ Replace _mapper.Map<T>(source) calls with source.MapToT()
  5. βœ… Build and verify

πŸ—‘οΈ Phase 4: Remove Old Mapper

Once all types are migrated:

# Remove old packages
dotnet remove package AutoMapper
dotnet remove package AutoMapper.Extensions.Microsoft.DependencyInjection
# or
dotnet remove package Mapster
dotnet remove package Mapster.DependencyInjection

# Remove DI registration
# Delete: services.AddAutoMapper(...) or services.AddMapster()

# Remove unused Profile/Config classes
# Delete: *Profile.cs, *MapperConfig.cs files

# Final build to confirm
dotnet build

πŸ’‘ Tips for a Smooth Migration

  • 🎯 Start with simple types - Migrate types with straightforward property-to-property mappings first. Leave complex mappings with custom resolvers for later.
  • πŸ”¨ Use dotnet build frequently - The Atc generator reports diagnostics at compile time. Build after each batch of changes to catch issues early.
  • πŸ” Leverage bidirectional mapping - If you had CreateMap<A, B>() and CreateMap<B, A>() (or ReverseMap()), replace both with a single [MapTo(typeof(B), Bidirectional = true)].
  • πŸ”„ Check enum mappings - Atc handles common enum name patterns automatically. Verify that None/Unknown, Active/Enabled mappings work as expected before removing manual enum converters.
  • πŸ“¦ Use configuration-based mapping for types you cannot modify - If you mapped 3rd-party types (EF Core entities, Protobuf messages, NuGet models), use the configuration-based approach instead of attributes. See Working with Object Mapping for details on configuration-based mapping.

🏠 Home

πŸ“– Getting Started

⚑ Generators

🎯 Examples

πŸ”— Integrations

πŸ” Reference

πŸ“‹ Feature Roadmaps


πŸ”— Resources

Clone this wiki locally