API Reference

Table of contents

  1. Namespace EggMapper
    1. MapperConfiguration
      1. Constructor
      2. Methods
    2. IMapper
      1. Methods
    3. IMapperConfigurationExpression
    4. IMappingExpression<TSrc, TDst>
    5. Profile
    6. MappingException
      1. Properties
    7. MappingValidationException
      1. Properties
  2. Extension Methods
    1. QueryableExtensions.ProjectTo<TSrc, TDst>()
  3. Namespace Microsoft.Extensions.DependencyInjection
    1. EggMapperServiceCollectionExtensions

Namespace EggMapper


MapperConfiguration

The root configuration object. Construct once at startup; keep as a singleton.

public sealed class MapperConfiguration

Constructor

public MapperConfiguration(Action<IMapperConfigurationExpression> configure)

Compiles all registered type maps into cached delegates during construction. This is the only expensive call — all subsequent mapping is near-zero overhead.

Example:

using EggMapper;

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Order, OrderDto>()
        .ForMember(d => d.CustomerName, o => o.MapFrom(s => s.Customer.FullName));

    cfg.CreateMap<OrderLine, OrderLineDto>();
    cfg.CreateMap<Customer, CustomerDto>();
    cfg.CreateMap<Address, AddressDto>();

    cfg.AddProfile<ProductProfile>();
});

Methods

Method Returns Description
CreateMapper() IMapper Returns a mapper backed by the compiled cache
AssertConfigurationIsValid() void Throws if any destination property is unmapped
BuildProjection<TSrc, TDst>() Expression<Func<TSrc, TDst>> Returns the raw projection expression for EF Core / LINQ

CreateMapper() example:

IMapper mapper = config.CreateMapper();

// mapper is lightweight — backed by the shared compiled cache
// Safe to call multiple times; each returns a new wrapper around the same cache

AssertConfigurationIsValid() example:

// In a unit test — catch missing maps before production
[Fact]
public void MappingConfiguration_ShouldBeValid()
{
    var config = new MapperConfiguration(cfg =>
        cfg.AddProfiles(typeof(OrderProfile).Assembly));

    config.AssertConfigurationIsValid(); // Throws on unmapped properties
}

BuildProjection<TSrc, TDst>() example:

// Get the raw expression for inspection or composition
Expression<Func<Order, OrderDto>> expr = config.BuildProjection<Order, OrderDto>();

// Use in custom LINQ queries
var results = dbContext.Orders
    .Where(o => o.IsActive)
    .Select(expr)
    .ToListAsync();

IMapper

The runtime mapping interface. Resolve from DI or call config.CreateMapper().

public interface IMapper

Methods

Signature Returns Description
Map<TSrc, TDst>(TSrc source) TDst Map source to a new destination instance
Map<TSrc, TDst>(TSrc source, TDst destination) TDst Map source into an existing destination instance
Map<TDst>(object source) TDst Map source (type inferred at runtime) to TDst
MapList<TSrc, TDst>(IEnumerable<TSrc> source) List<TDst> Batch map a collection using an inlined compiled loop
Patch<TSrc, TDst>(TSrc source, TDst destination) TDst Partial update — only non-null properties are copied

Map<TSrc, TDst>(source) — fastest path (static generic cache):

var dto = mapper.Map<Order, OrderDto>(order);
// Uses FastCache<Order, OrderDto> — zero dictionary lookup after first call

Map<TSrc, TDst>(source, destination) — map into existing object:

var existing = await db.Orders.FindAsync(id);
mapper.Map(updateRequest, existing);
// existing is populated in-place, no new allocation
await db.SaveChangesAsync();

Map<TDst>(object) — runtime type resolution:

// Useful when source type is not known at compile time
object entity = GetEntityFromSomewhere();
var dto = mapper.Map<OrderDto>(entity);
// Source type resolved via GetType() — falls back to base-type + interface walk

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.

MapList<TSrc, TDst>(source) — batch collection mapping:

var orders = await db.Orders.ToListAsync();
List<OrderDto> dtos = mapper.MapList<Order, OrderDto>(orders);
// Entire loop compiled as a single expression tree — near-manual speed

Patch<TSrc, TDst>(source, destination) — partial update:

// API endpoint for PATCH /products/{id}
app.MapPatch("/products/{id}", async (int id, UpdateProductRequest req, AppDbContext db, IMapper mapper) =>
{
    var product = await db.Products.FindAsync(id);
    if (product is null) return Results.NotFound();

    mapper.Patch(req, product); // Only non-null fields are written
    await db.SaveChangesAsync();
    return Results.NoContent();
});

IMapperConfigurationExpression

Fluent interface passed to the MapperConfiguration constructor callback.

Method Returns Description
CreateMap<TSrc, TDst>() IMappingExpression<TSrc, TDst> Register a type map
CreateMap(Type src, Type dst) IMappingExpression Register an open-generic type map
AddProfile<TProfile>() void Register all maps in a Profile subclass
AddProfile(Profile profile) void Register a pre-constructed Profile instance
AddProfiles(params Assembly[] assemblies) void Scan assemblies and register all Profile subclasses

CreateMap<TSrc, TDst>() example:

using EggMapper;

var config = new MapperConfiguration(cfg =>
{
    cfg.CreateMap<Customer, CustomerDto>();
    cfg.CreateMap<Address, AddressDto>();
});

CreateMap(Type, Type) — open generics:

using EggMapper;

var config = new MapperConfiguration(cfg =>
{
    // Maps Result<T> to ResultDto<T> for any T
    cfg.CreateMap(typeof(Result<>), typeof(ResultDto<>));
});

// Usage
var result = new Result<Order> { Data = order, Success = true };
var dto = mapper.Map<Result<Order>, ResultDto<Order>>(result);

AddProfiles(assemblies) example:

var config = new MapperConfiguration(cfg =>
    cfg.AddProfiles(
        typeof(OrderProfile).Assembly,
        typeof(ReportProfile).Assembly));

IMappingExpression<TSrc, TDst>

Fluent builder returned by CreateMap<TSrc, TDst>().

Method Description
ForMember(dst => dst.Prop, opt => opt.MapFrom(...)) Custom source expression
ForMember(dst => dst.Prop, opt => opt.Ignore()) Skip destination property
ForMember(dst => dst.Prop, opt => opt.Condition(s => ...)) Map only when predicate is true
ForMember(dst => dst.Prop, opt => opt.PreCondition(s => ...)) Skip source read when predicate is false
ForMember(dst => dst.Prop, opt => opt.NullSubstitute(value)) Use fallback when source is null
ForPath(dst => dst.A.B.Prop, opt => opt.MapFrom(...)) Map to a deeply nested destination path
ReverseMap() Also register the inverse mapping
BeforeMap(Action<TSrc, TDst>) Hook called before any property is mapped
AfterMap(Action<TSrc, TDst>) Hook called after all properties are mapped
MaxDepth(int depth) Limit recursion depth for self-referencing types
IncludeBase<TBaseSrc, TBaseDst>() Inherit the base type’s mapping rules
Validate(dst => dst.Prop, predicate, message) Post-mapping validation rule

ForMember with MapFrom — custom mapping expression:

cfg.CreateMap<Order, OrderDto>()
    .ForMember(d => d.CustomerName, o => o.MapFrom(s => s.Customer.FullName))
    .ForMember(d => d.TotalWithTax, o => o.MapFrom(s => s.Total * 1.1m))
    .ForMember(d => d.Status, o => o.MapFrom(s => s.Status.ToString()));

ForMember with Ignore — exclude sensitive data:

cfg.CreateMap<User, UserDto>()
    .ForMember(d => d.PasswordHash, o => o.Ignore())
    .ForMember(d => d.SecurityStamp, o => o.Ignore())
    .ForMember(d => d.TwoFactorSecret, o => o.Ignore());

ForMember with Condition — conditional mapping:

cfg.CreateMap<Product, ProductDto>()
    .ForMember(d => d.DiscountPrice,
               o => o.Condition(s => s.Discount > 0))
    .ForMember(d => d.WarehouseLocation,
               o => o.Condition((src, dst) => src.Stock > 0));

ForMember with NullSubstitute — fallback values:

cfg.CreateMap<Product, ProductDto>()
    .ForMember(d => d.Description, o => o.NullSubstitute("No description available"))
    .ForMember(d => d.ImageUrl, o => o.NullSubstitute("/images/placeholder.png"));

ForPath — nested destination paths:

cfg.CreateMap<OrderFlatDto, Order>()
    .ForPath(d => d.Customer.Name, o => o.MapFrom(s => s.CustomerName))
    .ForPath(d => d.Customer.Address.City, o => o.MapFrom(s => s.ShippingCity))
    .ForPath(d => d.Customer.Address.PostalCode, o => o.MapFrom(s => s.ShippingZip));

ReverseMap — bidirectional mapping:

cfg.CreateMap<Product, ProductDto>().ReverseMap();
// Equivalent to:
// cfg.CreateMap<Product, ProductDto>();
// cfg.CreateMap<ProductDto, Product>();

BeforeMap / AfterMap — lifecycle hooks:

cfg.CreateMap<Order, OrderDto>()
    .BeforeMap((src, dst) =>
    {
        // Normalize data before mapping
        src.CustomerName = src.CustomerName?.Trim();
    })
    .AfterMap((src, dst) =>
    {
        dst.MappedAt = DateTime.UtcNow;
        dst.DisplayId = $"ORD-{dst.Id:D6}";
    });

MaxDepth — self-referencing types:

// Category has Children: List<Category> (tree structure)
cfg.CreateMap<Category, CategoryDto>()
    .MaxDepth(3);
// Maps 3 levels deep, then stops (children beyond depth 3 are null)

IncludeBase — polymorphic inheritance:

cfg.CreateMap<Vehicle, VehicleDto>();
cfg.CreateMap<Car, CarDto>().IncludeBase<Vehicle, VehicleDto>();
cfg.CreateMap<Truck, TruckDto>().IncludeBase<Vehicle, VehicleDto>();

// When mapping a Car, base Vehicle properties are mapped via the base map
var car = new Car { Make = "Toyota", Doors = 4 };
var dto = mapper.Map<Car, CarDto>(car);
// dto.Make == "Toyota" (from Vehicle map), dto.Doors == 4 (from Car map)

Validate — post-mapping validation:

cfg.CreateMap<CreateOrderRequest, Order>()
    .Validate(d => d.CustomerName, n => !string.IsNullOrWhiteSpace(n), "Customer name is required")
    .Validate(d => d.Total, t => t > 0, "Order total must be positive")
    .Validate(d => d.Lines, l => l.Count > 0, "Order must have at least one line");

Profile

Base class for grouping related maps.

public abstract class Profile

Call CreateMap<TSrc, TDst>() inside your constructor to register maps. The API is identical to IMapperConfigurationExpression.

Example — real-world profile:

using EggMapper;

public class ECommerceProfile : Profile
{
    public ECommerceProfile()
    {
        // Order aggregate
        CreateMap<Order, OrderDto>()
            .ForMember(d => d.CustomerName, o => o.MapFrom(s => s.Customer.FullName))
            .ForMember(d => d.ShippingCity, o => o.MapFrom(s => s.ShippingAddress.City));

        CreateMap<Order, OrderSummaryDto>()
            .ForMember(d => d.LineCount, o => o.MapFrom(s => s.Lines.Count));

        CreateMap<OrderLine, OrderLineDto>();

        // Customer aggregate
        CreateMap<Customer, CustomerDto>()
            .ForMember(d => d.FullName, o => o.MapFrom(s => $"{s.FirstName} {s.LastName}"))
            .ForMember(d => d.PasswordHash, o => o.Ignore());

        CreateMap<Address, AddressDto>();

        // Reverse maps for write operations
        CreateMap<CreateOrderRequest, Order>()
            .ForMember(d => d.Id, o => o.Ignore())
            .ForMember(d => d.CreatedAt, o => o.Ignore());
    }
}

MappingException

Thrown when a mapping fails at runtime (e.g. unsupported type conversion, null reference in a non-nullable path).

public sealed class MappingException : Exception

Properties

Property Type Description
SourceType Type The source type being mapped
DestinationType Type The destination type
InnerException Exception? The underlying exception

Handling example:

try
{
    var dto = mapper.Map<Order, OrderDto>(order);
}
catch (MappingException ex)
{
    logger.LogError(ex,
        "Mapping failed: {Source} -> {Dest}",
        ex.SourceType.Name,
        ex.DestinationType.Name);
}

MappingValidationException

Thrown when post-mapping validation rules fail.

public sealed class MappingValidationException : Exception

Properties

Property Type Description
Errors IReadOnlyList<string> All validation messages that failed

Handling example:

try
{
    var order = mapper.Map<CreateOrderRequest, Order>(request);
}
catch (MappingValidationException ex)
{
    // Return all errors to the client
    return Results.ValidationProblem(
        ex.Errors.ToDictionary(
            e => "mapping",
            e => new[] { e }));
}

Extension Methods

QueryableExtensions.ProjectTo<TSrc, TDst>()

public static IQueryable<TDst> ProjectTo<TSrc, TDst>(
    this IQueryable<TSrc> source,
    MapperConfiguration config)

Builds a pure expression tree from the registered type map and passes it to IQueryable.Select(). The expression is never compiled by EggMapper — the LINQ provider translates it.

Example:

// All of these work with ProjectTo
var activeProducts = await dbContext.Products
    .Where(p => p.IsActive)
    .ProjectTo<Product, ProductDto>(config)
    .OrderBy(d => d.Name)
    .ToListAsync();

// With pagination
var page = await dbContext.Orders
    .Where(o => o.CustomerId == customerId)
    .OrderByDescending(o => o.CreatedAt)
    .ProjectTo<Order, OrderSummaryDto>(config)
    .Skip(pageSize * pageIndex)
    .Take(pageSize)
    .ToListAsync();

Namespace Microsoft.Extensions.DependencyInjection

EggMapperServiceCollectionExtensions

Extension method Description
AddEggMapper(params Assembly[]) Scan assemblies for Profile subclasses and register IMapper as singleton
AddEggMapper(Action<IMapperConfigurationExpression>) Inline configuration without profiles

Assembly scanning:

using EggMapper;

builder.Services.AddEggMapper(typeof(OrderProfile).Assembly);

Inline configuration:

using EggMapper;

builder.Services.AddEggMapper(cfg =>
{
    cfg.CreateMap<Order, OrderDto>();
    cfg.CreateMap<Customer, CustomerDto>();
});

Multiple assemblies:

builder.Services.AddEggMapper(
    typeof(OrderProfile).Assembly,       // Web API layer
    typeof(ReportProfile).Assembly);     // Reporting layer

Back to top

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