AutoMapper Migration Plan¶
Executive Summary¶
AutoMapper v15+ requires a commercial license for production use. This document evaluates open-source alternatives and provides a migration strategy for the Essert.MF project.
Status: COMPLETED (2026-03-05). All AutoMapper references have been removed. See DEBT-002 for the implementation record.
Recommendation: Migrate to Mapperly - a compile-time source generator with superior performance and active maintenance.
Table of Contents¶
- Background
- Current State Analysis
- Alternative Evaluation
- Recommended Solution
- Migration Strategy
- Risk Assessment
- Implementation Checklist
Background¶
The Problem¶
On April 16, 2025, Jimmy Bogard announced that AutoMapper would become commercial software under "Lucky Penny Software". The warning message appears in logs:
[WRN] You do not have a valid license key for the Lucky Penny software AutoMapper.
This is allowed for development and testing scenarios. If you are running in production
you are required to have a licensed version.
Options¶
- Purchase License: Continue with AutoMapper under commercial terms
- Stay on v14.0.0: Last free version (not recommended - security/compatibility risks)
- Migrate: Switch to an open-source alternative
Current State Analysis¶
AutoMapper Usage in Essert.MF¶
| Metric | Value |
|---|---|
| Package Version | AutoMapper v15.0.1 |
| Profile Classes | 2 |
| Total Profile Lines | 872 |
| Files Using IMapper | 34 |
| Value Object Mappings | 45+ |
| Parameter System Mappings | 14 |
| Custom Converters (ConvertUsing) | 123+ |
Profile Details¶
1. DomainToEfMappingProfile (260 lines)¶
Purpose: Maps between domain value objects and EF infrastructure entities
Mapping Categories: - Manufacturing: ProcessId, OrderNumber, SerialNumber, ProcessState - Product: ProductId, ArticleNumber, VersionNumber, VersionId - Robot: RobotId, RobotState - WorkPieceCarrier: WpcId, WpcNumber, WpcState, Position - Quality: MeasurementId, MeasurementType, MeasurementValue, QualityResult - Common: Email, Money, DateRange
Complex Converters:
// String parsing converters
ParsePosition() - "stationId,moduleId,positionId" format
ParseMeasurementValue() - "value|unit|decimalPlaces" format
ParseMoney() - "amount|currency" format
ParseDateRange() - "startDate|endDate" format
ParseProcessState() - Enum state mapping (0-4)
Entity Aggregate Mappings: - WorkPieceCarrier (Wpc → WorkPieceCarrierEntity) - Product (Productname → ProductEntity) - Robot (MasterData → Robot)
2. ParameterMappingProfile (612 lines)¶
Purpose: Handles Setup/Mapping pairs for 14 parameter systems
Systems Covered:
| System | Description | Complexity |
|---|---|---|
| TH | Tray Handling | High (Process, Shape, Position, Shift) |
| SC | Steep Conveyor | Medium |
| EGP | Robot Gripper (Schunk) | Medium |
| RFL | Refill Logic | Medium |
| XGX | XGX Vision (Keyence) | Medium |
| Kann | Stepper Motors (KocoMotion) | Medium |
| HP | Vibration Hopper (RNA) | Medium |
| ES | Essert Shaker | High (6 clip sequences) |
| CMMT | FESTO Servo Motors | Medium |
| CL | Circulation Logic | Medium |
| GJ | Gripperjaws | Low |
| EL | Essert Lighting | Low |
| IV3 | IV3 Vision (Keyence) | Low |
| Mfconfig | Microfactory Configuration | Medium |
Common Patterns:
- .ForMember() explicit field mappings (339+ occurrences)
- Navigation properties set to .Ignore()
- UID handling with special primary key logic
Injection Points¶
Infrastructure Layer:
├── Repositories (18 files)
│ ├── ParameterMappingRepository (base class)
│ ├── ThMappingRepository
│ ├── ScMappingRepository
│ └── ... (35 total repository files)
├── Services
└── Tests (6 files)
Alternative Evaluation¶
Candidates Considered¶
| Library | Type | Performance | License | Maintenance |
|---|---|---|---|---|
| Mapperly | Source Generator | Excellent | MIT | Active |
| Mapster | Runtime + Source Gen | Very Good | MIT | Seeking Maintainers |
| TinyMapper | Runtime | Good | MIT | Limited |
| AnyMapper | Runtime | Good | MIT | Community |
| Manual Mapping | Hand-written | Optimal | N/A | Self-maintained |
Detailed Comparison¶
Mapperly (Recommended)¶
Pros: - Compile-time code generation (no runtime reflection) - Fastest performance (338.5 ns vs AutoMapper's 1,203.9 ns) - Trimming and AOT safe - No runtime dependencies - Readable, debuggable generated code - Active development and strong community - MIT License
Cons: - Different API requires code changes - Each mapping needs explicit method definition - Learning curve for source generator patterns
Migration Effort: Medium-High
Mapster¶
Pros: - Similar API to AutoMapper (easier migration) - Good performance (49μs vs AutoMapper's 227μs) - Supports both runtime and compile-time generation - Simpler syntax for basic mappings
Cons: - Seeking maintainers (long-term support concern) - Less active development than Mapperly - Runtime reflection still used in standard mode
Migration Effort: Low-Medium
Performance Benchmarks¶
Benchmark Results (nanoseconds, lower is better):
Mapperly: 338.5 ns ████░░░░░░░░░░░░░░░░ (Fastest)
Manual: 529.6 ns ██████░░░░░░░░░░░░░░
AutoMapper: 1,203.9 ns █████████████░░░░░░░
Mapster: ~800 ns █████████░░░░░░░░░░░ (Estimated)
Recommended Solution¶
Primary Recommendation: Mapperly¶
Rationale: 1. Performance: 3.5x faster than AutoMapper 2. Safety: Compile-time validation catches mapping errors early 3. Future-proof: AOT/trimming compatible for .NET 9+ 4. Active maintenance: Strong community and regular updates 5. Architecture fit: Aligns with hexagonal architecture principles
Alternative: Mapster (If Rapid Migration Required)¶
Consider Mapster only if: - Immediate migration is critical - Team bandwidth is limited - Similar API reduces training time
Migration Strategy¶
Phase 1: Preparation (Week 1)¶
1.1 Add Mapperly Package¶
<!-- Essert.MF.Infrastructure.csproj -->
<PackageReference Include="Riok.Mapperly" Version="4.1.1" />
1.2 Create Mapper Structure¶
Essert.MF.Infrastructure/
└── Mapping/
├── DomainToEfMappingProfile.cs (existing - to be replaced)
├── ParameterMappingProfile.cs (existing - to be replaced)
├── Mappers/ (new folder)
│ ├── Domain/
│ │ ├── ProcessIdMapper.cs
│ │ ├── ProductMapper.cs
│ │ ├── WpcMapper.cs
│ │ └── ValueObjectMappers.cs
│ └── Parameters/
│ ├── ThMapper.cs
│ ├── ScMapper.cs
│ └── ... (one per system)
└── Converters/ (new folder)
├── PositionConverter.cs
├── MeasurementValueConverter.cs
└── MoneyConverter.cs
Phase 2: Value Object Mappers (Week 2)¶
2.1 Example: ProcessId Mapper¶
Before (AutoMapper):
CreateMap<long, ProcessId>().ConvertUsing(src => new ProcessId(src));
CreateMap<ProcessId, long>().ConvertUsing(src => src.Value);
After (Mapperly):
[Mapper]
public static partial class ProcessIdMapper
{
public static ProcessId ToProcessId(long value) => new ProcessId(value);
public static long ToLong(ProcessId processId) => processId.Value;
}
2.2 Example: Position Mapper (Complex)¶
Before (AutoMapper):
CreateMap<string, Position>().ConvertUsing(src => ParsePosition(src));
private static Position ParsePosition(string value)
{
var parts = value.Split(',');
return new Position(
int.Parse(parts[0]),
int.Parse(parts[1]),
int.Parse(parts[2]));
}
After (Mapperly):
[Mapper]
public static partial class PositionMapper
{
public static Position ToPosition(string value)
{
if (string.IsNullOrEmpty(value)) return Position.Empty;
var parts = value.Split(',');
return new Position(
int.Parse(parts[0]),
int.Parse(parts[1]),
int.Parse(parts[2]));
}
public static string ToString(Position position)
=> $"{position.StationId},{position.ModuleId},{position.PositionId}";
}
Phase 3: Entity Mappers (Week 3)¶
3.1 Example: Product Mapper¶
Before (AutoMapper):
CreateMap<Productname, ProductEntity>()
.ForMember(dest => dest.Uid, opt => opt.MapFrom(src => src.Uid))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Name))
.ForMember(dest => dest.Creator, opt => opt.MapFrom(src => src.Creator));
After (Mapperly):
[Mapper]
public partial class ProductMapper
{
[MapProperty(nameof(Productname.Uid), nameof(ProductEntity.Uid))]
[MapProperty(nameof(Productname.Name), nameof(ProductEntity.Name))]
[MapProperty(nameof(Productname.Creator), nameof(ProductEntity.Creator))]
public partial ProductEntity ToEntity(Productname product);
public partial Productname ToDomain(ProductEntity entity);
}
Phase 4: Parameter System Mappers (Weeks 4-5)¶
4.1 Example: TH Setup Mapper¶
Before (AutoMapper):
CreateMap<ThSetupDto, ThSetup>()
.ForMember(dest => dest.Uid, opt => opt.Ignore()) // AUTO_INCREMENT
.ForMember(dest => dest.Crc, opt => opt.Ignore())
.ForMember(dest => dest.ThMappings, opt => opt.Ignore())
// ... 20+ ForMember calls
After (Mapperly):
[Mapper]
public partial class ThSetupMapper
{
[MapperIgnoreTarget(nameof(ThSetup.Uid))]
[MapperIgnoreTarget(nameof(ThSetup.Crc))]
[MapperIgnoreTarget(nameof(ThSetup.ThMappings))]
public partial ThSetup ToEntity(ThSetupDto dto);
public partial ThSetupDto ToDto(ThSetup entity);
}
Phase 5: Repository Updates (Week 6)¶
5.1 Update Base Repository¶
Before:
public abstract class ParameterMappingRepository<TSetup, TMapping>
{
protected readonly IMapper Mapper;
public ParameterMappingRepository(IMapper mapper)
{
Mapper = mapper;
}
protected virtual TSetup MapToEntity(TSetupDto dto)
=> Mapper.Map<TSetup>(dto);
}
After:
public abstract class ParameterMappingRepository<TSetup, TMapping>
{
protected abstract TSetup MapToEntity(TSetupDto dto);
protected abstract TSetupDto MapToDto(TSetup entity);
}
// Each repository implements with specific mapper
public class ThMappingRepository : ParameterMappingRepository<ThSetup, ThMapping>
{
private readonly ThSetupMapper _setupMapper = new();
protected override ThSetup MapToEntity(ThSetupDto dto)
=> _setupMapper.ToEntity(dto);
}
Phase 6: Testing & Cleanup (Week 7)¶
- Update all unit tests
- Run integration tests
- Remove AutoMapper package reference
- Delete old profile classes
- Performance validation
Risk Assessment¶
High Risk Items¶
| Risk | Mitigation |
|---|---|
| Complex converters fail to migrate | Create custom converter classes for ParsePosition, ParseMoney, etc. |
| Navigation property handling differs | Use [MapperIgnoreTarget] consistently |
| Runtime behavior differences | Comprehensive integration testing |
Medium Risk Items¶
| Risk | Mitigation |
|---|---|
| Learning curve for team | Documentation and code examples |
| Increased code volume | Accept trade-off for compile-time safety |
| Test coverage gaps | Update DomainToEfMappingProfileTests |
Low Risk Items¶
| Risk | Mitigation |
|---|---|
| Package compatibility | Mapperly supports .NET 9.0 |
| DI integration changes | Mapperly supports constructor injection |
Implementation Checklist¶
Pre-Migration¶
- Review all 45+ value object mappings
- Document all custom converters
- Identify complex mapping patterns
- Set up branch for migration work
- Create test baseline
Phase 1: Setup¶
- Add Riok.Mapperly package
- Create Mappers folder structure
- Create Converters folder structure
Phase 2: Value Objects¶
- ProcessId, OrderNumber, SerialNumber
- ProductId, ArticleNumber, VersionNumber, VersionId
- RobotId, RobotState
- WpcId, WpcNumber, WpcState, Position
- MeasurementId, MeasurementType, MeasurementValue
- QualityResult, Email, Money, DateRange
Phase 3: Entities¶
- WorkPieceCarrierEntity mapper
- ProductEntity mapper
- Robot mapper
Phase 4: Parameter Systems¶
- TH (Tray Handling) - Setup + Mapping
- SC (Steep Conveyor) - Setup + Mapping
- EGP (Robot Gripper) - Setup + Mapping
- RFL (Refill Logic) - Setup + Mapping
- XGX (XGX Vision) - Setup + Mapping
- Kann (Stepper Motors) - Setup + Mapping
- HP (Vibration Hopper) - Setup + Mapping
- ES (Essert Shaker) - Setup + Mapping
- CMMT (FESTO Servo Motors) - Setup + Mapping
- CL (Circulation Logic) - Setup + Mapping
- GJ (Gripperjaws) - Setup + Mapping
- EL (Essert Lighting) - Setup + Mapping
- IV3 (IV3 Vision) - Setup + Mapping
- Mfconfig - Setup + Mapping
Phase 5: Repositories¶
- Update ParameterMappingRepository base class
- Update all 18 repository implementations
- Update ServiceCollectionExtensions
Phase 6: Testing & Cleanup¶
- Update DomainToEfMappingProfileTests
- Run all integration tests
- Run performance benchmarks
- Remove AutoMapper package
- Delete old profile classes
- Update documentation
References¶
Documentation¶
Articles¶
- Best Free Alternatives to AutoMapper in .NET
- AutoMapper Alternatives in .NET
- AutoMapper vs Mapster in .NET
- AutoMapper Goes Commercial - Here's Your Best Alternative
Alternative Libraries¶
Document History¶
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2026-01-22 | Claude Code | Initial plan created |