Getting Started

Installation

.NET CLI

dotnet add package EggMapper

Package Manager Console (Visual Studio)

Install-Package EggMapper

DI support (AddEggMapper()) is included in the main package. No separate package needed.


Your First Mapping

1 -- Define your types

public class Order
{
    public int Id { get; set; }
    public string CustomerName { get; set; } = "";
    public decimal Total { get; set; }
    public DateTime CreatedAt { get; set; }
}

public class OrderDto
{
    public int Id { get; set; }
    public string CustomerName { get; set; } = "";
    public decimal Total { get; set; }
    public DateTime CreatedAt { get; set; }
}

2 -- Create a configuration

using EggMapper;

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Order, OrderDto>();
});

The MapperConfiguration constructor compiles expression-tree delegates for every registered map. Keep a single long-lived instance (singleton).

3 -- Create a mapper and map

IMapper mapper = config.CreateMapper();

var order = new Order
{
    Id = 1,
    CustomerName = "Alice",
    Total = 99.99m,
    CreatedAt = DateTime.UtcNow
};

OrderDto dto = mapper.Map<Order, OrderDto>(order);
// dto.Id == 1, dto.CustomerName == "Alice", dto.Total == 99.99

You can also use the non-generic overload when the source type is not known at compile time:

// Source type inferred from the argument at runtime
OrderDto dto2 = mapper.Map<OrderDto>(order);

The generic Map<TSrc, TDst>() overload is faster than Map<TDst>(object) because it uses the static generic cache. Prefer the generic version in hot paths.


Mapping with Custom Members

When property names differ between source and destination, use ForMember:

var config = new MapperConfiguration(cfg =>
{
    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))
        .ForMember(d => d.InternalId, o => o.Ignore());
});

Mapping an Existing Destination

Pass a pre-existing destination object as the second argument to populate it instead of allocating a new one:

var existing = new OrderDto { Id = 99 };
mapper.Map(order, existing);   // existing is populated in-place
// existing.Id is now order.Id, existing.CustomerName is order.CustomerName, etc.

This is useful for updating tracked EF Core entities from a request DTO:

var entity = await db.Orders.FindAsync(id);
mapper.Map(updateDto, entity);
await db.SaveChangesAsync();

Mapping Collections

EggMapper maps common collection types automatically when the element map is registered:

Using MapList (recommended for performance)

List<Order> orders = await db.Orders.ToListAsync();

// Fully inlined compiled loop — near-manual speed
List<OrderDto> dtos = mapper.MapList<Order, OrderDto>(orders);

Using Map with collection types

cfg.CreateMap<Order, OrderDto>();

// Auto-detects collection types
var dtos = mapper.Map<List<OrderDto>>(orders);

Nested collections

// Customer has List<Order>, Order has List<OrderLine>
cfg.CreateMap<Customer, CustomerDto>();
cfg.CreateMap<Order, OrderDto>();
cfg.CreateMap<OrderLine, OrderLineDto>();

// Nested collections are mapped automatically
var dto = mapper.Map<Customer, CustomerDto>(customer);
// dto.Orders[0].Lines[0].ProductName is populated

Mapping Records and Immutable Types

EggMapper automatically selects the best-matching constructor:

public record OrderDto(int Id, string CustomerName, decimal Total);

cfg.CreateMap<Order, OrderDto>();
// Calls: new OrderDto(src.Id, src.CustomerName, src.Total)

Records with additional init properties:

public record ProductDto(int Id, string Name)
{
    public decimal Price { get; init; }
    public bool InStock { get; init; }
}

cfg.CreateMap<Product, ProductDto>();
// new ProductDto(src.Id, src.Name) { Price = src.Price, InStock = src.StockQuantity > 0 }

Using with DI (ASP.NET Core)

// Program.cs
builder.Services.AddEggMapper(typeof(OrderProfile).Assembly);

// Any service or controller
public class OrderService(IMapper mapper, AppDbContext db)
{
    public async Task<OrderDto> GetOrderAsync(int id)
    {
        var order = await db.Orders
            .Include(o => o.Customer)
            .Include(o => o.Lines)
            .FirstOrDefaultAsync(o => o.Id == id);

        return mapper.Map<Order, OrderDto>(order!);
    }

    public async Task<List<OrderSummaryDto>> GetRecentOrdersAsync()
    {
        var orders = await db.Orders
            .OrderByDescending(o => o.CreatedAt)
            .Take(50)
            .ToListAsync();

        return mapper.MapList<Order, OrderSummaryDto>(orders);
    }
}

Next Steps

TopicLink
Custom member mappings, ignores, reverse mapsAdvanced Features
Organise maps into reusable classesProfiles
Use EggMapper in ASP.NET Core, Blazor, gRPCDependency Injection
Full configuration optionsConfiguration
Compare with AutoMapperEggMapper vs AutoMapper
Benchmark methodology and resultsPerformance
Complete API surfaceAPI Reference