Migrating from AutoMapper
The goal is that changing using AutoMapper; to using Mapperion; resolves most files, and that
what is left is a short list you can work through in an afternoon.
What that is worth so far
There is a sample in the repository that configures one invoicing layer twice — once on AutoMapper 14, once on Mapperion — over a shared domain, maps the same invoices through both, and fails when the two disagree on any field. CI runs it.
The configuration it migrates uses profiles, value resolvers, a type converter, ForCtorParam
onto a positional record, NullSubstitute, Condition, Ignore, AfterMap,
ResolutionContext.Items filled per call, flattening three hops in, and an enum that only
crosses by name.
The entire diff between the two versions is one using per file and one !: Mapperion types
the items bag as IDictionary<string, object?> rather than IDictionary<string, object>, so a
cast out of it needs a null-forgiving operator under a nullable context.
Worth being straight about what that is not. It says the API takes the same calls and gives the same answers. It does not say anything about your build, your container wiring or your thirty profiles, and it was written by somebody who already knew both libraries. The shapes match; that is a smaller claim than "your migration will be easy".
The procedure
- Make sure your tests pass on AutoMapper first. You want a known-good starting point.
- Install
MapperionandMapperion.Extensions.DependencyInjectionalongside it. Both libraries can sit in a project at once — the namespaces and type names differ — so you can migrate module by module. - Replace
using AutoMapper;withusing Mapperion;. - Adjust the handful of renames in the table below.
- Compile. What still fails is a real difference; the table says which.
- Run
AssertIsValid(). It usually turns up pairs AutoMapper was resolving implicitly. - Run your tests, then remove AutoMapper.
What is the same
MapperConfiguration, Profile, CreateMap, ForMember, ForPath, ForCtorParam, MapFrom,
Ignore, Condition, PreCondition, NullSubstitute, ConvertUsing, ConstructUsing,
ReverseMap, Include, IncludeBase, IncludeMembers, BeforeMap, AfterMap, MaxDepth,
PreserveReferences, AllowNullCollections, ProjectTo, ITypeConverter, IValueConverter,
IValueResolver, IMappingAction, ResolutionContext.Items, SourceMemberNamingConvention,
DestinationMemberNamingConvention.
What is renamed
| AutoMapper | Mapperion |
|---|---|
AssertConfigurationIsValid() |
AssertIsValid(), and the long name works as an alias |
AddAutoMapper(...) |
AddMapperion(...) |
RecognizePrefixes / RecognizePostfixes |
RecognizeSourcePrefixes / RecognizeSourcePostfixes, because there are destination ones too |
AutoMapperConfigurationException |
MapperConfigurationException |
AutoMapperMappingException |
MappingException |
static Mapper.Map(...) from v4 |
MapperHost.Instance.Map(...), after MapperHost.Initialize |
What differs on purpose
A pair with no map is always an error. AutoMapper can be configured to map types you never
declared. Mapperion will not: an undeclared pair is a MapperConfigurationException, at startup
if you call AssertIsValid() and on first use otherwise. Surprises in production are worse than
a few extra CreateMap lines.
A looping object graph fails instead of taking the process down. If two maps reference each
other and the data has a cycle, AutoMapper recurses until the stack is gone, and a
StackOverflowException cannot be caught — that is
CVE-2026-32933, unfixed on its MIT line.
Mapperion counts the depth of any map that can reach itself and throws RecursionLimitException,
which derives from MappingException, past RecursionLimit. The default is 64, the same as
System.Text.Json. Only maps that close an unguarded loop pay for the counter.
ProjectTo reports what it cannot translate. AutoMapper silently skips converters, resolvers
and before/after steps in a projection, so the same map gives different answers through Map and
through ProjectTo. Mapperion throws instead, and says which member and why.
AfterMap cannot replace the destination. Use ConstructUsing, which exists with both of
AutoMapper's overloads. Declaring ConstructUsing and ForCtorParam on the same map is rejected
when the configuration is built, rather than the factory quietly winning.
Reading through a member counts as using it, when validating against the source member list.
Given ForMember(d => d.Anything, o => o.MapFrom(s => s.Depot.Code)), Depot is read, so
MemberListValidation.Source counts it as used. AutoMapper counts a path its naming convention
found but not one you wrote by hand, and reports Depot as unmapped — the two were run side by
side to establish that, and it reads more like an artefact of how it records what the convention
matched than a rule anybody chose.
The lenient reading is the default because being more lenient cannot break a migration: a
configuration AutoMapper accepted is accepted here. Set cfg.ReadingThroughAMemberUsesIt = false
for its behaviour exactly.
Validation is off by default, as in AutoMapper. cfg.ValidateOnBuild = true turns it on.
Not in the box
Nothing, as far as AutoMapper goes: everything it does has an equivalent here, ProjectTo for
EF6 included since 0.10.0, with its own test suite running on net472.
What a projection cannot do at all, in either library, is build a dictionary or dispatch to a derived map: its shape is fixed before a row is read. Mapperion says so rather than skipping it.