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
- Static Mapperly mappers — no DI wrapping needed. Repositories call static methods directly. Mapperly generates code at compile-time; DI indirection adds no value.
- Abstract mapping methods in base classes —
Repository<> and ParameterMappingRepository<> base classes get abstract mapping methods. Concrete repos implement them using their specific Mapperly mapper.
- 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 |
Productname ↔ Product including nested Versions |
Infrastructure/Mapping/Mappers/Domain/RobotEntityMapper.cs |
Create |
MasterData ↔ Robot |
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 Clipname ↔ ClipName 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
- Silent mapping behavior differences (null handling, defaults) — Mitigated by parallel tests in Phase 1 that compare Mapperly output against AutoMapper output before switching consumers.
- 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.
- Mapperly
partial void UpdateEntity(dto, entity) limitations — Verified: Mapperly supports this since v3.0+. Fallback: hand-write update methods.
- Navigation property
[MapperIgnoreTarget] edge cases — Verify at Phase 2 build. Mapperly supports this since v3.0+.
- 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
dotnet build — solution compiles without errors (every phase)
dotnet test — all 1,390+ tests pass (every phase)
grep -r "AutoMapper" — zero references after Phase 6
- Performance benchmark — no regression after Phase 6