Indiko.Blocks.Mapping.SimpleMapper 2.2.18

dotnet add package Indiko.Blocks.Mapping.SimpleMapper --version 2.2.18
                    
NuGet\Install-Package Indiko.Blocks.Mapping.SimpleMapper -Version 2.2.18
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Indiko.Blocks.Mapping.SimpleMapper" Version="2.2.18" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Indiko.Blocks.Mapping.SimpleMapper" Version="2.2.18" />
                    
Directory.Packages.props
<PackageReference Include="Indiko.Blocks.Mapping.SimpleMapper" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Indiko.Blocks.Mapping.SimpleMapper --version 2.2.18
                    
#r "nuget: Indiko.Blocks.Mapping.SimpleMapper, 2.2.18"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Indiko.Blocks.Mapping.SimpleMapper@2.2.18
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Indiko.Blocks.Mapping.SimpleMapper&version=2.2.18
                    
Install as a Cake Addin
#tool nuget:?package=Indiko.Blocks.Mapping.SimpleMapper&version=2.2.18
                    
Install as a Cake Tool

Indiko.Blocks.Mapping.SimpleMapper

High-performance object-to-object mapper that compiles all mapping logic to native delegates at startup, producing zero-overhead mapping at runtime.

Features

  • Compiled expression trees — mappings are compiled once to Func<TSource, TDest> delegates at configuration time; runtime mapping is a plain delegate invocation
  • Convention mapping — properties with matching names and compatible types are mapped automatically without any configuration
  • Profile-based organisation — group related mappings into MappingProfile classes and scan them by assembly
  • Rich member configurationMapFrom, Ignore, Condition, NullSubstitute, ReverseMap
  • Type converters, value resolvers, value converters — pluggable extension points for complex transformations
  • Collection mappingList<T>, arrays, and nested collections handled automatically
  • Nested object mapping — full object-graph mapping with circular reference tracking
  • Expression mapping — translate Expression<Func<TModel, TResult>> to Expression<Func<TEntity, TResult>> for EF Core query composition
  • IQueryable projectionProjectTo<TDto> for server-side SQL projections
  • No external dependencies — only Microsoft.Extensions.DependencyInjection.Abstractions and the BCL

Installation

dotnet add package Indiko.Blocks.Mapping.SimpleMapper

Quick Start

1. Register with the DI container

using Indiko.Blocks.Mapping.SimpleMapper.Extensions;

// Assembly scan — discovers all MappingProfile subclasses automatically
services.AddSimpleMapper(cfg =>
{
    cfg.AddExpressionMapping();               // optional: enables Expression<Func<>> translation
    cfg.AddMaps(Assembly.GetEntryAssembly()); // scan for MappingProfile subclasses
});

2. Define a profile

using Indiko.Blocks.Mapping.SimpleMapper.Profiles;

public class OrderProfile : MappingProfile
{
    public OrderProfile()
    {
        CreateMap<Order, OrderDto>()
            .ForMember(d => d.CustomerName, opt => opt.MapFrom(s => s.Customer.FullName))
            .ForMember(d => d.Total,        opt => opt.MapFrom(s => s.Lines.Sum(l => l.Price * l.Qty)))
            .ReverseMap();
    }
}

3. Inject IMapper and map

using Indiko.Blocks.Mapping.SimpleMapper.Core;

public class OrderService
{
    private readonly IMapper _mapper;
    public OrderService(IMapper mapper) => _mapper = mapper;

    public OrderDto GetDto(Order order) => _mapper.Map<OrderDto>(order);
}

Convention Mapping

Properties whose names match (case-insensitive) and whose types are compatible are mapped without any configuration:

// No ForMember needed — Id, Name, CreatedAt map by convention
CreateMap<Product, ProductDto>();

Supported automatic coercions:

Source type Destination type
string int, long, double, decimal, Guid, bool, DateTime
Numeric types Compatible numeric types
Enum int / string (and reverse)
T T? (nullable lift)

Member Configuration

MapFrom — custom source expression

CreateMap<User, UserDto>()
    .ForMember(d => d.FullName,    opt => opt.MapFrom(s => s.First + " " + s.Last))
    .ForMember(d => d.City,        opt => opt.MapFrom(s => s.Address.City))
    .ForMember(d => d.OrderCount,  opt => opt.MapFrom(s => s.Orders.Count));

Ignore — exclude a member

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

Condition — map only when predicate is true

CreateMap<Product, ProductDto>()
    .ForMember(d => d.DiscountedPrice,
        opt => opt.Condition(s => s.Discount > 0));

NullSubstitute — provide a default for null source values

CreateMap<Article, ArticleDto>()
    .ForMember(d => d.Description, opt => opt.NullSubstitute("No description available"))
    .ForMember(d => d.Tags,        opt => opt.NullSubstitute(new List<string>()));

ReverseMap

CreateMap<Order, OrderDto>().ReverseMap();
// Generates the reverse TypeMap using convention mapping

All IMapper Overloads

// New instance — destination type inferred from runtime type
TDest Map<TDest>(object source);

// New instance — fully typed
TDest Map<TSource, TDest>(TSource source);

// Update existing instance
TDest Map<TSource, TDest>(TSource source, TDest destination);

// With per-call hooks and Items bag
TDest Map<TDest>(object source,
    Action<IMappingOperationOptions<object, TDest>> opts);

TDest Map<TSource, TDest>(TSource source,
    Action<IMappingOperationOptions<TSource, TDest>> opts);

TDest Map<TSource, TDest>(TSource source, TDest destination,
    Action<IMappingOperationOptions<TSource, TDest>> opts);

Per-call hooks

var dto = _mapper.Map<Order, OrderDto>(order, opts =>
{
    opts.BeforeMap((src, dest) => dest.MappedAt = DateTimeOffset.UtcNow);
    opts.AfterMap((src, dest)  => _logger.LogDebug("Mapped Order {Id}", src.Id));
    opts.Items["tenantId"] = _tenantContext.TenantId; // pass data to resolvers
});

Collections

Collections of any registered TypeMap are mapped automatically:

// Maps each element using the Order → OrderDto TypeMap
List<OrderDto>  dtos  = _mapper.Map<List<OrderDto>>(orderList);
OrderDto[]      arr   = _mapper.Map<OrderDto[]>(orderList);

Nested collection properties are also handled:

CreateMap<Customer, CustomerDto>();
// CustomerDto.Orders (List<OrderDto>) mapped via Order → OrderDto TypeMap

Nested Objects

Nested objects are mapped recursively. Register a TypeMap for each type that appears in the graph:

CreateMap<Address, AddressDto>();
CreateMap<Customer, CustomerDto>();
// CustomerDto.Address is mapped via Address → AddressDto TypeMap automatically

Circular references (e.g. Order → Customer → Orders) are tracked by object identity and handled safely without infinite loops.

Type Converters

Implement ITypeConverter<TSource, TDest> for full type-to-type conversion:

public class MoneyConverter : ITypeConverter<decimal, MoneyDto>
{
    public MoneyDto Convert(decimal source, MoneyDto dest, ResolutionContext ctx)
        => new MoneyDto { Amount = source, Currency = "EUR" };
}

// Register in profile:
CreateMap<decimal, MoneyDto>().ConvertUsing<MoneyConverter>();

Value Resolvers

Implement IValueResolver<TSource, TDest, TMember> for complex single-member resolution:

public class FullNameResolver : IValueResolver<User, UserDto, string>
{
    public string Resolve(User source, UserDto dest, string member, ResolutionContext ctx)
        => $"{source.Title} {source.First} {source.Last}".Trim();
}

// Register in profile:
CreateMap<User, UserDto>()
    .ForMember(d => d.FullName, opt => opt.ResolveUsing<FullNameResolver>());

Value Converters

Implement IValueConverter<TSource, TDest> for reusable per-member value conversions:

public class DateToStringConverter : IValueConverter<DateTime, string>
{
    public string Convert(DateTime source, ResolutionContext ctx)
        => source.ToString("yyyy-MM-dd");
}

CreateMap<Event, EventDto>()
    .ForMember(d => d.Date, opt => opt.ConvertUsing(new DateToStringConverter(), s => s.StartDate));

Expression Mapping

Translate Expression<Func<TModel, TResult>> to Expression<Func<TEntity, TResult>> for use with EF Core Where/OrderBy without loading entities into memory.

Enable in DI registration

services.AddSimpleMapper(cfg =>
{
    cfg.AddExpressionMapping(); // required
    cfg.AddMaps(assembly);
});

Usage

// Expression written against the DTO/model type
Expression<Func<OrderDto, bool>> modelFilter = dto => dto.CustomerName.Contains("Smith");

// Translated to an entity expression usable with EF Core
Expression<Func<Order, bool>> entityFilter =
    _mapper.MapExpression<OrderDto, Order, bool>(modelFilter);

var results = await _db.Orders.Where(entityFilter).ToListAsync();

Supported node types

  • Property access chains (dto.Customer.City)
  • Binary comparisons (==, !=, <, >, &&, ||)
  • String methods (Contains, StartsWith, EndsWith)
  • Collection predicates (Any, All, Count, Where) with nested lambda remapping
  • Constant, closure, and MemberInit expressions
  • Nullable type lifting (GuidGuid? coercion)

IQueryable Projection

ProjectTo<TDto> pushes the mapping directly into the SQL query — only the columns needed by the DTO are selected:

// Server-side projection — generates SELECT only the columns OrderDto needs
var dtos = await _db.Orders
    .Where(o => o.CustomerId == customerId)
    .ProjectTo<OrderDto>(_mapper)
    .ToListAsync(cancellationToken);

This avoids loading full entity graphs when you only need a subset of properties.

Configuration Validation

Call AssertConfigurationIsValid() at startup or in CI to catch misconfigured mappings before runtime:

// In Program.cs / startup
var config = new MapperConfiguration(cfg => cfg.AddMaps(assembly));
config.AssertConfigurationIsValid(); // throws if any destination member is unmapped

// In unit tests (recommended for CI)
[Fact]
public void All_profiles_are_valid()
{
    var config = new MapperConfiguration(cfg =>
        cfg.AddMaps(typeof(OrderProfile).Assembly));

    config.AssertConfigurationIsValid(); // fast fail in CI
}

Testing

Instantiate the mapper without DI for focused unit tests:

var config = new MapperConfiguration(cfg => cfg.AddProfile<OrderProfile>());
IMapper mapper = new Mapper(config);

var dto = mapper.Map<OrderDto>(new Order { Id = 1, Customer = new Customer { FullName = "Alice" } });
dto.CustomerName.Should().Be("Alice");

Test a profile in isolation with only the profiles it depends on:

[Fact]
public void OrderProfile_maps_correctly()
{
    var config = new MapperConfiguration(cfg =>
    {
        cfg.AddProfile<CustomerProfile>(); // dependency
        cfg.AddProfile<OrderProfile>();
    });
    config.AssertConfigurationIsValid();

    var mapper = new Mapper(config);
    var order  = new Order { Id = 42, Lines = [new OrderLine { Price = 10, Qty = 3 }] };
    var dto    = mapper.Map<OrderDto>(order);

    dto.Id.Should().Be(42);
    dto.Total.Should().Be(30m);
}

Best Practices

  • Register MapperConfiguration as a singleton — it is thread-safe and expensive to construct; never create it per-request.
  • One profile per bounded contextOrderProfile, CustomerProfile, InventoryProfile.
  • Explicitly ignore security-sensitive membersopt.Ignore() on PasswordHash, SecurityStamp, etc. Documents intent and satisfies validation.
  • Prefer ProjectTo over in-memory Select + Map for IQueryable queries — avoids loading full entities.
  • Keep MapFrom expressions pure and translatable — avoid method calls that EF Core cannot translate to SQL when the TypeMap is used with ProjectTo or MapExpression.
  • Register child TypeMaps for every type in collection properties that appears in expression-mapped predicates.

Target Framework

  • .NET 10
  • Indiko.Blocks.Mediation.SimpleMediator — lightweight CQRS mediator
  • Indiko.Blocks.Mediation.Abstractions — shared mediation contracts

License

MIT — see LICENSE in the repository root.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Indiko.Blocks.Mapping.SimpleMapper:

Package Downloads
Indiko.Blocks.DataAccess.Abstractions

Building Blocks DataAccess Abstractions

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.2.18 0 3/8/2026
2.2.17 0 3/8/2026
2.2.16 0 3/8/2026
2.2.15 32 3/7/2026
2.2.13 28 3/7/2026
2.2.12 29 3/7/2026
2.2.10 33 3/6/2026
2.2.9 33 3/6/2026
2.2.8 32 3/6/2026
2.2.7 34 3/6/2026
2.2.5 38 3/6/2026
2.2.3 36 3/6/2026
2.2.2 33 3/6/2026
2.2.1 31 3/6/2026
2.2.0 33 3/6/2026