-
Notifications
You must be signed in to change notification settings - Fork 0
Migration Guide
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.
- πΊοΈ Working with Object Mapping - Full feature reference and usage guide
- π» Sample Projects - Working code examples with architecture diagrams
- Migration Guide - Object Mapping Generator
- π 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
NonetoUnknown,ActivetoEnabled, 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).
| 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 | β | β | β | β |
AutoMapper uses runtime reflection and a fluent configuration API. Migrating to Atc Mapping Generator replaces runtime mapping with compile-time generated code.
| 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 1: ποΈ Remove AutoMapper packages
dotnet remove package AutoMapper
dotnet remove package AutoMapper.Extensions.Microsoft.DependencyInjectionStep 2: π¦ Add Atc Source Generators
dotnet add package Atc.SourceGenerators
dotnet add package Atc.SourceGenerators.AnnotationsStep 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 methodsStep 6: π¨ Build and fix any diagnostics
dotnet buildThe compiler will report ATCMAP001 (class must be partial), ATCMAP002 (invalid target type), or ATCMAP003 (property not found) diagnostics for any issues.
| 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")] |
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.
| 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 |
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 Unknown4. π¦ 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 detailsMapster uses runtime reflection with a fluent configuration API, similar to AutoMapper but with a different syntax. Migration follows a similar pattern.
| 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 1: ποΈ Remove Mapster packages
dotnet remove package Mapster
dotnet remove package Mapster.DependencyInjectionStep 2: π¦ Add Atc Source Generators
dotnet add package Atc.SourceGenerators
dotnet add package Atc.SourceGenerators.AnnotationsStep 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 buildReview any ATCMAP001, ATCMAP002, or ATCMAP003 diagnostics and fix accordingly.
You do not need to migrate everything at once. The Atc Mapping Generator can coexist with AutoMapper, Mapperly, or Mapster during a gradual migration.
| 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 |
Install Atc packages without removing the existing mapper:
dotnet add package Atc.SourceGenerators
dotnet add package Atc.SourceGenerators.AnnotationsBoth mapping systems can coexist. AutoMapper/Mapster use IMapper via DI, while Atc generates extension methods in the Atc.Mapping namespace. There are no conflicts.
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();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:
- βοΈ Add
partialkeyword to the class declaration - π·οΈ Add
[MapTo(typeof(Target))]attribute - π§ Add
[MapIgnore]and[MapProperty]as needed - π Replace
_mapper.Map<T>(source)calls withsource.MapToT() - β Build and verify
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- π― Start with simple types - Migrate types with straightforward property-to-property mappings first. Leave complex mappings with custom resolvers for later.
- π¨ Use
dotnet buildfrequently - 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>()andCreateMap<B, A>()(orReverseMap()), 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/Enabledmappings 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
- π Migration Guide
- π Working with Dependency Registration
- βοΈ Working with Options Binding
- πΊοΈ Working with Object Mapping
- π Working with Enum Mapping
- π Working with Annotation Constants
- π¦ Sample Projects
- πΎ PetStore API Example
- π Attribute API Reference
- π©Ί Diagnostics Reference
β οΈ Common Patterns and Anti-Patterns- π Native AOT Compatibility
- π§ Troubleshooting
- π Dependency Registration Feature Roadmap
- βοΈ Options Binding Feature Roadmap
- πΊοΈ Object Mapping Feature Roadmap
- π Annotation Constants Feature Roadmap
- π¦ GitHub Repository
- π₯ NuGet Package
- π Report Issues