Skip to content

DEBT-002: AutoMapper to Mapperly Migration

ID: DEBT-002 Status: Done Created: 2026-03-04 Updated: 2026-03-05 Priority: Medium

URS References

URS ID Requirement Impact
No URS impact (pure infrastructure concern)

Implementation Progress

Phase Description Status Commit
0 Preparation & package setup Done 25aeb77
1 Value object & domain entity mappers Done f5c7446
2 Parameter & system parameter mappers Done 1de62b0
3 Refactor base repositories & wire consumers Done 61b0780
4 DI registration & API cleanup Done d165b50
5 Test migration Done 040af03
6 Final cleanup — remove AutoMapper Done 42ebbeb

Summary

AutoMapper v15+ requires a commercial license (since April 2025). Essert.MF uses AutoMapper v15.0.1 across 41 files, 3 mapping profiles (1,194 lines). The migration target is Mapperly — a compile-time source generator (MIT license, 3.5x faster, AOT-safe).

Current State

Metric Value
AutoMapper version v15.0.1
Profiles 3 (DomainToEfMappingProfile, ParameterMappingProfile, SystemParameterMappingProfile)
Total profile lines 1,194
Files using IMapper 41
Value object mappings 45+ (22 bidirectional pairs)
Parameter systems 22
Custom parse helpers 5 (Position, MeasurementValue, Money, DateRange, ProcessState)

Key Design Decisions

  1. Static Mapperly mappers — no DI wrapping needed. Repositories call static methods directly. Mapperly generates code at compile-time; DI indirection adds no value.
  2. Abstract mapping methods in base classesRepository<> and ParameterMappingRepository<> base classes get abstract mapping methods. Concrete repos implement them using their specific Mapperly mapper.
  3. Dual-stack transition — AutoMapper and Mapperly coexist during Phases 0-5. Phase 6 removes AutoMapper entirely.

Existing Analysis

Detailed alternative evaluation and migration examples: docs/archive/AUTOMAPPER_MIGRATION_PLAN.md


Phase 0: Preparation (no behavior change)

Goal: Add Mapperly package, create folder structure, create requirement doc. Status: Done (commit 25aeb77, 2026-03-04) Estimated impact: 2 modified files, 1 new package, folder creation

0.1 Infrastructure Changes

File Action Description
Essert.MF.Infrastructure/Essert.MF.Infrastructure.csproj Modify Add <PackageReference Include="Riok.Mapperly" Version="4.1.1" /> alongside existing AutoMapper
Essert.MF.Infrastructure/Mapping/Mappers/Domain/ Create Folder for domain/value object mappers
Essert.MF.Infrastructure/Mapping/Mappers/Parameters/ Create Folder for parameter system mappers
Essert.MF.Infrastructure/Mapping/Mappers/SystemParameter/ Create Folder for system parameter mappers

0.2 Documentation Changes

File Action Description
docs/requirements/DEBT-002-automapper-to-mapperly.md Create This document
docs/requirements/BACKLOG.md Modify Move DEBT-002 to "Active & Planned"

0.3 Verification

dotnet build Essert.MF.Infrastructure   # builds with both packages
dotnet test                              # all 1,390+ tests pass (no behavior change)

Phase 1: Value Object & Domain Entity Mappers (additive only)

Goal: Create all Mapperly mappers equivalent to DomainToEfMappingProfile.cs (261 lines). No consumers changed — new files only. Status: Done (2026-03-05) Actual impact: 5 new files, 57 new tests (all passing in parallel with 58 existing AutoMapper tests)

1.1 New Mapper Files

File Action Description
Infrastructure/Mapping/Mappers/Domain/ValueObjectMappers.cs Create [Mapper] partial class with 22 bidirectional value object conversions + 5 hand-written parse helpers
Infrastructure/Mapping/Mappers/Domain/WpcEntityMapper.cs Create Wpc EF ↔ WorkPieceCarrier domain (hand-written, replicates existing static helper)
Infrastructure/Mapping/Mappers/Domain/ProductEntityMapper.cs Create ProductnameProduct including nested Versions
Infrastructure/Mapping/Mappers/Domain/RobotEntityMapper.cs Create MasterDataRobot

1.2 New Test Files

File Action Description
Infrastructure.Tests/Unit/Mapping/MapperlyValueObjectTests.cs Create Mirror all test cases from DomainToEfMappingProfileTests.cs using new Mapperly mappers

1.3 Value Object Mapping Inventory

Domain Value Objects
Manufacturing ProcessId ↔ long, OrderNumber ↔ string, SerialNumber ↔ string, ProcessState ↔ int
Product ProductId ↔ long, ArticleNumber ↔ string, VersionNumber ↔ string, VersionId ↔ long
Robot RobotId ↔ long, RobotState ↔ int
WorkPieceCarrier WpcId ↔ long, WpcNumber ↔ string, WpcState ↔ int, Position ↔ string (complex parser)
Quality MeasurementId ↔ long, MeasurementType ↔ string, MeasurementValue ↔ string (complex parser)
Common QualityResult ↔ int, Email ↔ string, Money ↔ string (complex parser), DateRange ↔ string (complex parser)

1.4 Source Pattern Reference

Read Infrastructure/Mapping/DomainToEfMappingProfile.cs for all mapping logic to replicate.

1.5 Verification

dotnet build Essert.MF.Infrastructure           # source generators produce code
dotnet test Essert.MF.Infrastructure.Tests       # new + existing tests pass in parallel

Phase 2: Parameter & System Parameter Mappers (additive only)

Goal: Create Mapperly mapper classes for all 22 parameter systems + 8 system parameter types. No consumers changed — new files only. Status: Done (2026-03-04) Actual impact: 23 new files (22 parameter mappers + 1 system parameter mapper), 0 modified files

Key design decisions: - All mappers use [Mapper(RequiredMappingStrategy = RequiredMappingStrategy.Target)] to match AutoMapper's behavior of silently ignoring unmapped source properties (entity navigation properties have no DTO counterpart). - Entity properties that exist on the EF entity but not on the DTO (e.g., newer columns like EgpSetup.Softgrip, ThShape.RowCount) are explicitly ignored via [MapperIgnoreTarget]. - ES clip mapper uses [MapProperty] for the ClipnameClipName rename. - Each mapper provides 3 methods per type pair: ToEntity, ToDto, UpdateEntity (for existing-target mapping).

2.1 Parameter Mapper Files (22 files)

Each file follows the pattern:

[Mapper]
public static partial class ThParameterMapper
{
    [MapperIgnoreTarget(nameof(ThProcess.ThMappings))]   // navigation properties
    public static partial ThProcess ToEntity(ThProcessDto dto);
    public static partial ThProcessDto ToDto(ThProcess entity);
    [MapperIgnoreTarget(nameof(ThProcess.ThMappings))]
    public static partial void UpdateEntity(ThProcessDto dto, ThProcess entity);
    // Same for Mapping type...
}

File System Complexity
Mapping/Mappers/Parameters/ThParameterMapper.cs Tray Handling High (Process, Shape, Position, Shift sub-types)
Mapping/Mappers/Parameters/ScParameterMapper.cs Steep Conveyor Medium
Mapping/Mappers/Parameters/EgpParameterMapper.cs Robot Gripper (Schunk) Medium
Mapping/Mappers/Parameters/RflParameterMapper.cs Refill Logic Medium
Mapping/Mappers/Parameters/XgxParameterMapper.cs XGX Vision (Keyence) Medium
Mapping/Mappers/Parameters/KannParameterMapper.cs Stepper Motors (KocoMotion) Medium
Mapping/Mappers/Parameters/HpParameterMapper.cs Vibration Hopper (RNA) Medium
Mapping/Mappers/Parameters/EsParameterMapper.cs Essert Shaker High (6 clip sequences)
Mapping/Mappers/Parameters/CmmtParameterMapper.cs FESTO Servo Motors Medium
Mapping/Mappers/Parameters/ClParameterMapper.cs Circulation Logic Medium
Mapping/Mappers/Parameters/GjParameterMapper.cs Gripperjaws Low
Mapping/Mappers/Parameters/ElParameterMapper.cs Essert Lighting Low
Mapping/Mappers/Parameters/Iv3ParameterMapper.cs IV3 Vision (Keyence) Low
Mapping/Mappers/Parameters/MfconfigParameterMapper.cs Microfactory Config Medium
Mapping/Mappers/Parameters/MfprocessParameterMapper.cs MF Process Medium
Mapping/Mappers/Parameters/ProcessParameterMapper.cs Process Medium
Mapping/Mappers/Parameters/EgkParameterMapper.cs EGK Medium
Mapping/Mappers/Parameters/DistancesensorParameterMapper.cs Distance Sensor Medium
Mapping/Mappers/Parameters/Pickit3dParameterMapper.cs Pickit3D Medium
Mapping/Mappers/Parameters/SchmalzscpsiParameterMapper.cs Schmalz SCP Si Medium
Mapping/Mappers/Parameters/LightingParameterMapper.cs Lighting Low
Mapping/Mappers/Parameters/VsParameterMapper.cs Vibration System Medium

2.2 System Parameter Mapper (1 file)

File Action Description
Mapping/Mappers/SystemParameter/SystemParameterMapper.cs Create 8 entity type pairs (Assemblyunit, SpCmmt, Posaxis, Positioningunit, SpRobots, Traystacker, SpUnspecified, Wpcopener)

2.3 Source Pattern Reference

Read Infrastructure/Mapping/ParameterMappingProfile.cs (879 lines) and SystemParameterMappingProfile.cs (55 lines) for all ForMember/Ignore patterns to replicate as [MapperIgnoreTarget] attributes.

2.4 Verification

dotnet build Essert.MF.Infrastructure   # all source generators compile

Phase 3: Refactor Base Repositories & Wire Consumers

Goal: Remove IMapper from all repositories, wire to Mapperly mappers. This is the largest structural change. Status: Done (commit 61b0780, 2026-03-05) Actual impact: 36 modified files (28 repositories + UnitOfWork + 7 integration test files + 1 unit test file)

3.1 Base Class Changes

File Action Description
Repositories/Repository.cs Modify Remove IMapper Mapper field + constructor param. Change MapToDomain, MapToEf, MapToEf(domain, ef) from virtual → abstract
Repositories/ParameterMappingRepository.cs Modify Remove IMapper Mapper field + constructor param. Add 4 abstract methods: MapSetupToDto, MapSetupToEntity, MapMappingToDto, MapMappingsToDto. Replace 5 Mapper.Map<>() calls (lines 66, 81, 137, 238)

3.2 Core Domain Repository Changes (4 files)

File Action Description
Repositories/ProductRepository.cs Modify Remove IMapper, implement abstracts via ProductEntityMapper
Repositories/WorkPieceCarrierRepository.cs Modify Remove IMapper, implement via WpcEntityMapper
Repositories/RobotRepository.cs Modify Remove IMapper, implement via RobotEntityMapper
Repositories/ManufacturingProcessRepository.cs Modify Remove IMapper, implement via ValueObjectMappers

3.3 Parameter Repository Changes (21 files)

Each parameter repository: - Remove IMapper mapper constructor parameter - Implement 4 abstract mapping methods using its Phase 2 Mapperly mapper

File Mapperly Mapper
Repositories/ThMappingRepository.cs ThParameterMapper
Repositories/ScMappingRepository.cs ScParameterMapper
Repositories/EgpMappingRepository.cs EgpParameterMapper
Repositories/RflMappingRepository.cs RflParameterMapper
Repositories/XgxMappingRepository.cs XgxParameterMapper
Repositories/KannMappingRepository.cs KannParameterMapper
Repositories/HpMappingRepository.cs HpParameterMapper
Repositories/EsMappingRepository.cs EsParameterMapper
Repositories/CmmtMappingRepository.cs CmmtParameterMapper
Repositories/ClMappingRepository.cs ClParameterMapper
Repositories/GjMappingRepository.cs GjParameterMapper
Repositories/ElMappingRepository.cs ElParameterMapper
Repositories/Iv3MappingRepository.cs Iv3ParameterMapper
Repositories/MfconfigMappingRepository.cs MfconfigParameterMapper
Repositories/MfprocessMappingRepository.cs MfprocessParameterMapper
Repositories/ProcessMappingRepository.cs ProcessParameterMapper
Repositories/EgkMappingRepository.cs EgkParameterMapper
Repositories/DistancesensorMappingRepository.cs DistancesensorParameterMapper
Repositories/Pickit3dMappingRepository.cs Pickit3dParameterMapper
Repositories/SchmalzscpsiMappingRepository.cs SchmalzscpsiParameterMapper
Repositories/LightingMappingRepository.cs LightingParameterMapper

3.4 System Parameter Repository (1 file)

File Action Description
Repositories/SystemParameterRepository.cs Modify Replace _mapper.Map<>() with SystemParameterMapper.ToDto() static calls

3.5 Additional Consumers (discovered during implementation)

File Action Description
UnitOfWork/UnitOfWork.cs Modify Remove IMapper field, constructor param, and all _mapper arguments from repository instantiation
Tests/Unit/Repositories/RepositoryTests.cs Modify Remove IMapper mock, update TestRepository to use abstract mapping methods, remove null-mapper test
Tests/Integration/Mapping/ParameterMappingIntegrationTests.cs Modify Remove _mapper from 10 repository constructor calls
Tests/Integration/.../ElParameterRepositoryIntegrationTests.cs Modify Remove _mapper from constructor call
Tests/Integration/.../GjParameterRepositoryIntegrationTests.cs Modify Remove _mapper from constructor call
Tests/Integration/.../Iv3ParameterRepositoryIntegrationTests.cs Modify Remove _mapper from constructor call
Tests/Integration/.../ProcessMappingRepositoryIntegrationTests.cs Modify Remove _mapper from constructor call
Tests/Integration/.../MfconfigRepositoryIntegrationTests.cs Modify Remove _mapper from constructor call
Tests/Integration/.../MfprocessMappingRepositoryIntegrationTests.cs Modify Remove _mapper from constructor call

3.6 Verification

dotnet build Essert.MF.Infrastructure
dotnet test --filter "FullyQualifiedName~Infrastructure.Tests"
dotnet test --filter "FullyQualifiedName~API.Rest.Tests"

Phase 4: DI Registration & API Cleanup

Goal: Remove AutoMapper from DI registration and all API startup paths. Status: Done (2026-03-05) Actual impact: 4 modified files

4.1 Changes

File Action Description
Infrastructure/DependencyInjection/ServiceCollectionExtensions.cs Modify Remove services.AddAutoMapper(cfg => cfg.AddMaps(...)) call
Infrastructure/ServiceCollectionExtensions.cs Modify Remove using AutoMapper, AddAutoMapper registration
Infrastructure/Repositories/SystemParameterRepository.cs Modify Remove IMapper dependency; replace _mapper.Map() with Entry().CurrentValues.SetValues() for generic update and SystemParameterMapper static calls for 8 typed DTO methods
Infrastructure/Repositories/StatisticsRepository.cs Modify Remove unused using AutoMapper

Notes: - API Program.cs files (REST, OpcUa, GraphQL) had no AutoMapper code — no changes needed. - SystemParameterRepository was missed in Phase 3 because it uses a different pattern (standalone CRUD, not parameter-mapping base class). Its generic UpdateWithCrcAsync<T> now uses EF Core's CurrentValues.SetValues() instead of AutoMapper for same-type property copying.

4.2 Verification

dotnet build                             # entire solution — 0 warnings, 0 errors
dotnet test Essert.MF.Domain.Tests       # 263 passed
dotnet test Essert.MF.Application.Tests  # 826 passed
dotnet test --filter "FullyQualifiedName~Infrastructure.Tests.Unit"  # 68 passed

Phase 5: Test Migration

Goal: Rewrite all test files that directly reference AutoMapper. Status: Done (2026-03-05) Actual impact: 10 modified files (1 major rewrite, 1 moderate, 8 minor cleanup)

5.1 Major Rewrites

File Action Description
Infrastructure.Tests/Unit/Mapping/DomainToEfMappingProfileTests.cs Rewrite (401 lines) Replace _mapper.Map<>() with static Mapperly calls. Remove MapperConfiguration + AssertConfigurationIsValid(). Keep all test data and assertions.

5.2 Moderate Changes

File Action Description
Infrastructure.Tests/Unit/Repositories/RepositoryTests.cs Modify Remove Mock<IMapper>, update TestRepository to implement abstract mapping methods
Infrastructure.Tests/Integration/Mapping/ParameterMappingIntegrationTests.cs Modify Remove IMapper from all repository construction
Infrastructure.Tests/Unit/Extensions/ServiceCollectionExtensionsTests.cs Modify Remove AutoMapper registration assertions

5.3 Minor Changes (remove unused IMapper field, AutoMapper init block, and usings)

File Action
Infrastructure.Tests/Integration/Persistence/Repositories/RepositoryIntegrationTests.cs Modify
Infrastructure.Tests/Integration/Persistence/Repositories/GjParameterRepositoryIntegrationTests.cs Modify
Infrastructure.Tests/Integration/Persistence/Repositories/ElParameterRepositoryIntegrationTests.cs Modify
Infrastructure.Tests/Integration/Persistence/Repositories/Iv3ParameterRepositoryIntegrationTests.cs Modify
Infrastructure.Tests/Integration/Persistence/Repositories/ProcessMappingRepositoryIntegrationTests.cs Modify
Infrastructure.Tests/Integration/Persistence/Repositories/MfconfigRepositoryIntegrationTests.cs Modify
Infrastructure.Tests/Integration/Persistence/Repositories/MfprocessMappingRepositoryIntegrationTests.cs Modify

5.4 Verification

dotnet build Essert.MF.Infrastructure.Tests   # 0 warnings, 0 errors
dotnet test Essert.MF.Domain.Tests            # 263 passed
dotnet test Essert.MF.Application.Tests       # 826 passed
dotnet test --filter "FullyQualifiedName~Infrastructure.Tests.Unit"  # 68 passed

Phase 6: Final Cleanup — Remove AutoMapper

Goal: Remove all traces of AutoMapper from the codebase. Status: Done (2026-03-05) Actual impact: 4 deleted files, 2 modified .csproj

6.1 Delete Old Profiles

File Action
Infrastructure/Mapping/DomainToEfMappingProfile.cs Delete
Infrastructure/Mapping/ParameterMappingProfile.cs Delete
Infrastructure/Mapping/SystemParameterMappingProfile.cs Delete

6.2 Remove Package References

File Action Description
Essert.MF.Infrastructure/Essert.MF.Infrastructure.csproj Modify Remove AutoMapper package reference
Essert.MF.Infrastructure.Tests/Essert.MF.Infrastructure.Tests.csproj Modify Remove AutoMapper package reference

6.3 Cleanup

  • Remove all remaining using AutoMapper; statements
  • Delete Infrastructure.Tests/Unit/Mapping/MapperlyValueObjectTests.cs if merged into rewritten profile tests

6.4 Documentation Updates

File Action Description
docs/AUTOMAPPER_MIGRATION_PLAN.md Modify Mark as completed
docs/requirements/BACKLOG.md Modify Move DEBT-002 to "Completed"
This document Modify Mark all phases as Complete

6.5 Final Verification

dotnet build                                                        # entire solution, zero warnings
dotnet test                                                         # all 1,390+ tests pass
grep -r "AutoMapper" --include="*.cs" --include="*.csproj" .       # returns nothing (excluding bin/obj)
cd Essert.MF.PerformanceBenchmark && dotnet run -- run              # no performance regression

Dependency Graph

Phase 0 ──► Phase 1 ──► Phase 2 ──► Phase 3 ──► Phase 4 ──► Phase 5 ──► Phase 6
 (setup)    (domain     (param      (wire all    (DI         (tests)     (remove
             mappers)    mappers)    repos)       cleanup)                AutoMapper)

Phases 1 and 2 are additive-only (no existing code modified). Phase 3 is the structural pivot point — the largest and riskiest phase. Phases 4-6 are cleanup.

Risks and Considerations

  1. Silent mapping behavior differences (null handling, defaults) — Mitigated by parallel tests in Phase 1 that compare Mapperly output against AutoMapper output before switching consumers.
  2. Phase 3 is a 28-file change — Can be split into sub-commits: core repos first (4 files), then parameter repos alphabetically. Each sub-commit must build and pass tests.
  3. Mapperly partial void UpdateEntity(dto, entity) limitations — Verified: Mapperly supports this since v3.0+. Fallback: hand-write update methods.
  4. Navigation property [MapperIgnoreTarget] edge cases — Verify at Phase 2 build. Mapperly supports this since v3.0+.
  5. Test rewrite misses edge cases — Old AutoMapper tests remain until Phase 6 deletion; both test suites run in parallel during Phases 1-5.

Verification Plan

  1. dotnet build — solution compiles without errors (every phase)
  2. dotnet test — all 1,390+ tests pass (every phase)
  3. grep -r "AutoMapper" — zero references after Phase 6
  4. Performance benchmark — no regression after Phase 6