EggMapper vs AutoMapper

Why Switch from AutoMapper?

AutoMapper changed to a commercial license (RPL) starting v13. EggMapper is a free, MIT-licensed drop-in replacement that is also 2-5x faster with zero extra allocations. Whether you need to replace AutoMapper due to the license change or you are evaluating mappers for a new project, EggMapper gives you the same API at significantly higher performance. Migration takes about 5 minutes.

Feature Comparison

FeatureAutoMapper 16.xEggMapper
LicenseRPL 1.5 (commercial)MIT (free forever)
PerformanceBaseline2-5x faster
AllocationsExtra per-mapZero extra
Runtime reflectionYesNo (compiled expressions)
APIOriginalSame API — drop-in
CreateMap / ForMemberYesYes (identical)
ProfileYesYes (identical)
IMapperYesYes (identical)
DI registrationAddAutoMapper()AddEggMapper()
EF Core ProjectToProjectTo<D>(cfg)ProjectTo<S,D>(cfg)
Null collectionsEmpty by defaultEmpty by default
Unmatched dest collectionEmpty by defaultEmpty by default (top-level + nested)
Read-only / internal-set IgnoreAllowedAllowed; non-Ignore ops throw at config time
Custom IEnumerable wrappersConstructed via ctorConstructed via cached interface ctor
EF Core proxiesSupportedSupported
Same-type T to TNeeds CreateMapAuto-compiles
Collection auto-mapAutomaticAutomatic
Batch MapListNot built-inBuilt-in (inlined loop)
Patch/partialNot built-inBuilt-in
Inline validationNot built-inBuilt-in

Performance Comparison

EggMapper consistently outperforms AutoMapper on every scenario. Measured with BenchmarkDotNet on .NET 10 (x64):

ScenarioEggMapperAutoMapperSpeedupEggMapper AllocAutoMapper Alloc
Flat mapping (10 props)15 ns35 ns2.3x104 B232 B
Nested objects (2 levels)25 ns70 ns2.8x248 B504 B
Flattening (8 props)18 ns50 ns2.8x104 B232 B
Collection (100 items)1.5 us5 us3.3x10,824 B14,424 B
Deep collection (100 nested)3 us12 us4.0x34,424 B52,824 B
Large collection (1000 items)15 us50 us3.3x108,024 B144,024 B

EggMapper matches hand-written (manual) mapping allocation in every scenario. The only allocation is the destination object itself.

These numbers are representative. Run benchmarks on your own hardware: cd src/EggMapper.Benchmarks && dotnet run -c Release -f net10.0 -- --filter *

Why EggMapper is Faster

  1. Inlined nested maps — AutoMapper calls a separate delegate for each nested object. EggMapper embeds the child mapping directly into the parent expression tree, eliminating delegate invocation overhead.

  2. Inlined collection loopsMapList<S,D>() compiles the entire for loop and element mapping as a single expression tree. No enumerator allocation, no per-element delegate call.

  3. Static generic cachingFastCache<TSource, TDestination> is a static generic class. The JIT bakes the cache field address directly into calling code. No dictionary lookup, no hash computation.

  4. Context-free delegates — For common maps (flat, nested, flattening), EggMapper compiles Func<TSource, TDestination> with zero boxing. AutoMapper always passes through a ResolutionContext.

  5. Zero allocations — No per-map allocations beyond the destination object itself. AutoMapper allocates ResolutionContext and intermediate collections.

Allocation Comparison

// BenchmarkDotNet results — Flat mapping, 10 properties
// Lower is better

|     Method |     Mean | Allocated |
|----------- |---------:|----------:|
|     Manual |   12 ns  |     104 B |  <-- hand-written code
|  EggMapper |   15 ns  |     104 B |  <-- matches manual
| AutoMapper |   35 ns  |     232 B |  <-- 128 B extra per map
|    Mapster |   20 ns  |     104 B |
|   Mapperly |   13 ns  |     104 B |  <-- source generator (compile-time)

EggMapper matches manual code allocation in every scenario. The only allocation is the destination object itself.

Migration Guide (5 minutes)

Step 1: Swap the NuGet Package

- dotnet add package AutoMapper
- dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection
+ dotnet add package EggMapper

Step 2: Find and Replace Namespace

- using AutoMapper;
+ using EggMapper;

Step 3: Update DI Registration

- services.AddAutoMapper(typeof(MyProfile).Assembly);
+ services.AddEggMapper(typeof(MyProfile).Assembly);

Step 4: Update ProjectTo Calls

EggMapper's ProjectTo takes both source and destination type parameters:

- query.ProjectTo<ProductDto>(config);
+ query.ProjectTo<Product, ProductDto>(config);

Step 5: Run Tests

All your existing CreateMap, ForMember, Profile, and IMapper code works unchanged.

API Differences

Most of the API is identical. Here are the differences worth knowing:

AutoMapperEggMapperNotes
ProjectTo<TDest>(config)ProjectTo<TSrc, TDest>(config)Explicit source type
AddAutoMapper(assemblies)AddEggMapper(assemblies)Same behavior
IMapper is scopedIMapper is singletonSafe because config is immutable
CreateMap<T,T>() required for cloneAuto-compiles without CreateMapJust call Map<T,T>(obj)
No built-in Patchmapper.Patch(source, dest)Partial update
No built-in MapListmapper.MapList<S,D>(list)Batch collection

Migration Examples

ForMember with MapFrom

using EggMapper;

// Identical in both libraries
cfg.CreateMap<Customer, CustomerDto>()
    .ForMember(d => d.FullName, o => o.MapFrom(s => $"{s.FirstName} {s.LastName}"))
    .ForMember(d => d.City, o => o.MapFrom(s => s.Address.City));

Ignore

// Identical in both libraries
cfg.CreateMap<User, UserDto>()
    .ForMember(d => d.PasswordHash, o => o.Ignore());

ReverseMap

// Identical in both libraries
cfg.CreateMap<Order, OrderDto>().ReverseMap();

Profile

// Identical in both libraries
public class OrderProfile : Profile
{
    public OrderProfile()
    {
        CreateMap<Order, OrderDto>();
        CreateMap<OrderLine, OrderLineDto>();
    }
}

Conditions

// Identical in both libraries
cfg.CreateMap<Product, ProductDto>()
    .ForMember(d => d.Price,
               o => o.Condition(s => s.Price > 0));

BeforeMap / AfterMap

// Identical in both libraries
cfg.CreateMap<Order, OrderDto>()
    .AfterMap((src, dst) => dst.MappedAt = DateTime.UtcNow);

MaxDepth (self-referencing types)

// Identical in both libraries
cfg.CreateMap<Category, CategoryDto>()
    .MaxDepth(3);

Common Migration Gotchas

ProjectTo requires both type parameters in EggMapper. If you have ProjectTo<Dto>(config), the compiler error will point you to the fix.

  • ProjectTo signature — AutoMapper uses ProjectTo<TDest>(config), EggMapper uses ProjectTo<TSrc, TDest>(config). This is the most common compile error after migration.
  • DI method nameAddAutoMapper() becomes AddEggMapper(). The parameters are identical.
  • No behavioral differencesForMember, Ignore, Condition, ReverseMap, BeforeMap, AfterMap, MaxDepth, IncludeBase all behave identically.
  • Same-type mapping — If you have CreateMap<T, T>() calls for cloning, you can remove them. EggMapper auto-compiles these on first use.

Ready to Migrate?

dotnet add package EggMapper

Most migrations are a find-and-replace. The entire process typically takes 5 minutes for a medium-sized project.

Quick Start Guide | API Reference | GitHub