EggMapper vs AutoMapper

Table of contents

  1. Why Switch from AutoMapper?
  2. Feature Comparison
  3. Performance Comparison
    1. Why EggMapper is Faster
    2. Allocation Comparison
  4. Migration Guide (5 minutes)
    1. Step 1: Swap the NuGet Package
    2. Step 2: Find and Replace Namespace
    3. Step 3: Update DI Registration
    4. Step 4: Update ProjectTo Calls
    5. Step 5: Run Tests
  5. API Differences
  6. Migration Examples
    1. ForMember with MapFrom
    2. Ignore
    3. ReverseMap
    4. Profile
    5. Conditions
    6. BeforeMap / AfterMap
    7. MaxDepth (self-referencing types)
  7. Common Migration Gotchas
  8. Ready to Migrate?

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

Feature AutoMapper 16.x EggMapper
License RPL 1.5 (commercial) MIT (free forever)
Performance Baseline 2-5x faster
Allocations Extra per-map Zero extra
Runtime reflection Yes No (compiled expressions)
API Original Same API — drop-in
CreateMap / ForMember Yes Yes (identical)
Profile Yes Yes (identical)
IMapper Yes Yes (identical)
DI registration AddAutoMapper() AddEggMapper()
EF Core ProjectTo ProjectTo<D>(cfg) ProjectTo<S,D>(cfg)
Null collections Empty by default Empty by default
EF Core proxies Supported Supported
Same-type T to T Needs CreateMap Auto-compiles
Collection auto-map Automatic Automatic
Batch MapList Not built-in Built-in (inlined loop)
Patch/partial Not built-in Built-in
Inline validation Not built-in Built-in

Performance Comparison

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

Scenario EggMapper AutoMapper Speedup EggMapper Alloc AutoMapper Alloc
Flat mapping (10 props) 15 ns 35 ns 2.3x 104 B 232 B
Nested objects (2 levels) 25 ns 70 ns 2.8x 248 B 504 B
Flattening (8 props) 18 ns 50 ns 2.8x 104 B 232 B
Collection (100 items) 1.5 us 5 us 3.3x 10,824 B 14,424 B
Deep collection (100 nested) 3 us 12 us 4.0x 34,424 B 52,824 B
Large collection (1000 items) 15 us 50 us 3.3x 108,024 B 144,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:

AutoMapper EggMapper Notes
ProjectTo<TDest>(config) ProjectTo<TSrc, TDest>(config) Explicit source type
AddAutoMapper(assemblies) AddEggMapper(assemblies) Same behavior
IMapper is scoped IMapper is singleton Safe because config is immutable
CreateMap<T,T>() required for clone Auto-compiles without CreateMap Just call Map<T,T>(obj)
No built-in Patch mapper.Patch(source, dest) Partial update
No built-in MapList mapper.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.


Back to top

EggMapper — MIT licensed © Eggspot. Fastest .NET runtime object mapper.