EggMapper vs AutoMapper
Table of contents
- Why Switch from AutoMapper?
- Feature Comparison
- Performance Comparison
- Migration Guide (5 minutes)
- API Differences
- Migration Examples
- Common Migration Gotchas
- 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
-
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.
-
Inlined collection loops —
MapList<S,D>()compiles the entireforloop and element mapping as a single expression tree. No enumerator allocation, no per-element delegate call. -
Static generic caching —
FastCache<TSource, TDestination>is a static generic class. The JIT bakes the cache field address directly into calling code. No dictionary lookup, no hash computation. -
Context-free delegates — For common maps (flat, nested, flattening), EggMapper compiles
Func<TSource, TDestination>with zero boxing. AutoMapper always passes through aResolutionContext. -
Zero allocations — No per-map allocations beyond the destination object itself. AutoMapper allocates
ResolutionContextand 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.
ProjectTosignature — AutoMapper usesProjectTo<TDest>(config), EggMapper usesProjectTo<TSrc, TDest>(config). This is the most common compile error after migration.- DI method name —
AddAutoMapper()becomesAddEggMapper(). The parameters are identical. - No behavioral differences —
ForMember,Ignore,Condition,ReverseMap,BeforeMap,AfterMap,MaxDepth,IncludeBaseall 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.