HyperMapper.Core 12.2.1

dotnet add package HyperMapper.Core --version 12.2.1
                    
NuGet\Install-Package HyperMapper.Core -Version 12.2.1
                    
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="HyperMapper.Core" Version="12.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="HyperMapper.Core" Version="12.2.1" />
                    
Directory.Packages.props
<PackageReference Include="HyperMapper.Core" />
                    
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 HyperMapper.Core --version 12.2.1
                    
#r "nuget: HyperMapper.Core, 12.2.1"
                    
#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 HyperMapper.Core@12.2.1
                    
#: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=HyperMapper.Core&version=12.2.1
                    
Install as a Cake Addin
#tool nuget:?package=HyperMapper.Core&version=12.2.1
                    
Install as a Cake Tool

HyperMapper v12

100% AutoMapper API Compatible - Drop-in replacement with Source Generator support for maximum performance.

HyperMapper is a high-performance object mapping library designed to be fully compatible with AutoMapper's API while providing significantly better performance through Source Generators. It was created specifically to be a drop-in replacement for AutoMapper, requiring only a namespace change to migrate.

Architecture Overview

┌─────────────────────────────────────────────────────────────────────────────┐
│                         HyperMapper v12 Architecture                         │
└─────────────────────────────────────────────────────────────────────────────┘

   ┌──────────────────────┐
   │   Your Application   │
   │   using HyperMapper; │
   └──────────┬───────────┘
              │
              ├─────────────────────┬─────────────────────┐
              │                     │                     │
              ▼                     ▼                     ▼
   ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
   │   IMapper API    │  │  MapperConfig    │  │   Profile API    │
   │                  │  │                  │  │                  │
   │ • Map<T>()       │  │ • AddProfile<>() │  │ • CreateMap<>()  │
   │ • Map<S,D>()     │  │ • AddMaps()      │  │ • ForMember()    │
   │ • Map(s, d)      │  │ • Validate()     │  │ • ReverseMap()   │
   └────────┬─────────┘  └────────┬─────────┘  └────────┬─────────┘
            │                     │                     │
            └─────────────────────┴─────────────────────┘
                                  │
                    ┌─────────────▼─────────────┐
                    │   Dual Execution Engine   │
                    └─────────────┬─────────────┘
                                  │
            ┌─────────────────────┴─────────────────────┐
            │                                           │
            ▼                                           ▼
┌───────────────────────┐                  ┌───────────────────────┐
│   RUNTIME MODE        │                  │   CODEGEN MODE        │
│   (Dynamic)           │                  │   (Static)            │
├───────────────────────┤                  ├───────────────────────┤
│                       │                  │                       │
│ Configuration Phase:  │                  │ Compile-Time Phase:   │
│ • Profile Analysis    │                  │ • Roslyn Analyzer     │
│ • TypeMap Building    │                  │ • Syntax Analysis     │
│                       │                  │ • Code Generation     │
│ Compilation Phase:    │                  │                       │
│ • Expression Trees    │                  │ Generated Output:     │
│ • IL Compilation      │                  │ • .g.cs files         │
│ • Generic Cache       │                  │ • Static methods      │
│                       │                  │ • Registry class      │
│ Runtime Execution:    │                  │                       │
│ • Delegate invoke     │                  │ Runtime Execution:    │
│ • ~120ns per map      │                  │ • Direct method call  │
│ • 1-5ms warm-up       │                  │ • ~44ns per map       │
│                       │                  │ • Zero warm-up        │
└───────────────────────┘                  └───────────────────────┘
            │                                           │
            │                                           │
            └────────────────┬──────────────────────────┘
                             │
                             ▼
                  ┌──────────────────────┐
                  │   Mapped Objects     │
                  │   (Your DTOs/Models) │
                  └──────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│                          Performance Comparison                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  Manual Code:      ██ 22ns                                                  │
│  HyperMapper CG:   ███ 32ns       ← 1.4x slower than manual ✅              │
│  HyperMapper RT:   ███████████████ 147ns  ← 6.7x slower than manual        │
│  AutoMapper:       ████████████████ 170ns  ← 7.7x slower than manual       │
│                                                                              │
│  🎉 CodeGen is now 4.6x FASTER than Runtime!                                │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│                           Key Differentiators                                │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ✅ 100% AutoMapper API Compatible  → Drop-in replacement                   │
│  ⚡ Dual-Mode Architecture           → Runtime OR CodeGen                    │
│  🚀 Source Generator Support         → Compile-time code generation          │
│  🎯 Zero Reflection (CodeGen)        → AOT/Native ready                      │
│  🔥 Zero Warm-up (CodeGen)           → Fast from first call                  │
│  📦 .NET 8+ Compatible               → Modern .NET support                   │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Key Features

  • 100% AutoMapper API Compatible - Same interfaces, same methods, same behavior
  • 1.2x Faster than AutoMapper with Runtime Mode
  • 🚀 Up to 5.4x Faster with CodeGen Mode (Source Generators)
  • 🔥 4.6x Faster CodeGen vs Runtime - Use the fast path with typed API
  • 🛡️ Compile-Time Safety - Catch mapping errors before runtime
  • 🎯 AOT/Native Ready - Full Native AOT compilation support
  • 📦 .NET 8+ Compatible - Works with .NET 8, 9, 10, and future versions
  • 🧪 Extensively Tested - 856 tests with 82.4% code coverage

Performance Comparison

Environment: macOS, Apple M2 Pro, .NET 8.0.23, BenchmarkDotNet v0.14.0

Scenario Manual HyperMapper Runtime HyperMapper CodeGen AutoMapper Best Choice
Simple Mapping 22 ns 147 ns 32 ns 170 ns CodeGen (1.4x)
Complex Object (Full) 161 ns 295 ns 149 ns 370 ns CodeGen (-7%)
Complex Object (Sparse) 86 ns 200 ns 85 ns 246 ns CodeGen (-1%)
Collection (1000 items) 18,914 ns 34,548 ns 29,935 ns 37,402 ns CodeGen (1.6x)
Flattening 51 ns 161 ns 75 ns 202 ns CodeGen (2.1x)
Deep Nesting (10 levels) 230 ns 343 ns 255 ns 365 ns CodeGen (1.3x)

Key Performance Insights

  1. HyperMapper CodeGen is FASTER in ALL scenarios after API optimization
  2. HyperMapper CodeGen is 4.6x faster than Runtime on simple mappings (32ns vs 147ns)
  3. HyperMapper CodeGen allocates 30% less memory than manual (184B vs 264B on complex objects)
  4. HyperMapper CodeGen is 2.5x faster than AutoMapper on complex objects (149ns vs 370ns)
  5. HyperMapper CodeGen is 2.7x faster than Runtime on flattening (75ns vs 161ns)
  6. Always use CodeGen Mode for production - best performance across all scenarios

Table of Contents


Installation

Runtime Mode Setup

Add the project reference to your .csproj:

<ItemGroup>
  <ProjectReference Include="../HyperMapper/src/HyperMapper/HyperMapper.csproj" />
</ItemGroup>

This is all you need for Runtime Mode (AutoMapper-compatible API).

To enable compile-time code generation with Source Generators:

1. Add HyperMapper reference (runtime library):

<ItemGroup>
  <ProjectReference Include="../HyperMapper/src/HyperMapper/HyperMapper.csproj" />
</ItemGroup>

2. Add Source Generator (analyzer reference):

<ItemGroup>
  <ProjectReference Include="../HyperMapper.SourceGenerator/HyperMapper.SourceGenerator.csproj"
                    OutputItemType="Analyzer"
                    ReferenceOutputAssembly="false" />
</ItemGroup>

3. Optional: Enable generated file inspection (for debugging):

<PropertyGroup>
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
  <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

4. Build your project - .g.cs files will be generated automatically in obj/Generated/

5. Use generated mappers (after first build):

using HyperMapper.Generated;

var config = new MapperConfiguration(cfg => {
    cfg.AddProfile<YourProfile>();
});

HyperMapperGeneratedRegistry.Initialize(config);  // ← Register generated mappers
var mapper = config.CreateMapper();

See examples/HyperMapper.Examples.CodeGen for a complete working example.

Dependency Injection Registration

using HyperMapper;
using HyperMapper.Generated; // For CodeGen Mode

services.AddSingleton<IMapper>(sp =>
{
    var loggerFactory = sp.GetService<ILoggerFactory>();
    var config = new MapperConfiguration(cfg =>
    {
        cfg.AddProfile<MyMappingProfile>();
    }, loggerFactory);

    // Optional: Register compile-time generated mappers for maximum performance
    HyperMapperGeneratedRegistry.Initialize(config);

    config.AssertConfigurationIsValid();
    return config.CreateMapper();
});

Quick Start

Runtime Mode (AutoMapper-Compatible)

Perfect for rapid development and 100% AutoMapper compatibility:

using HyperMapper;

// 1. Define your classes
public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
}

public class UserDto
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public int Age { get; set; }
}

// 2. Create a Profile
public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<User, UserDto>()
            .ForMember(d => d.FullName, opt => opt.MapFrom(s =>
                $"{s.FirstName} {s.LastName}"))
            .ForMember(d => d.Age, opt => opt.MapFrom(s =>
                CalculateAge(s.BirthDate)));
    }

    private static int CalculateAge(DateTime birthDate)
    {
        var today = DateTime.Today;
        var age = today.Year - birthDate.Year;
        if (birthDate.Date > today.AddYears(-age)) age--;
        return age;
    }
}

// 3. Configure and use
var config = new MapperConfiguration(cfg =>
{
    cfg.AddProfile<UserProfile>();
});

var mapper = config.CreateMapper();

var user = new User
{
    Id = 1,
    FirstName = "John",
    LastName = "Doe",
    BirthDate = new DateTime(1990, 5, 15)
};

var userDto = mapper.Map<UserDto>(user);
// userDto.FullName = "John Doe"
// userDto.Age = 34 (calculated)

Performance: ~147ns per mapping

CodeGen Mode (Source Generators)

For production applications requiring maximum performance:

using HyperMapper;
using HyperMapper.Generated;

// 1. Same Profile class as Runtime Mode
public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<User, UserDto>()
            .ForMember(d => d.FullName, opt => opt.MapFrom(s =>
                $"{s.FirstName} {s.LastName}"))
            .ForMember(d => d.Age, opt => opt.MapFrom(s =>
                CalculateAge(s.BirthDate)));
    }
}

// 2. Configure with generated mappers
var config = new MapperConfiguration(cfg =>
{
    cfg.AddProfile<UserProfile>();
});

// CRITICAL: Register generated mappers
HyperMapperGeneratedRegistry.Initialize(config);

var mapper = config.CreateMapper();

// 3. Usage is identical
var userDto = mapper.Map<User, UserDto>(user);  // ✅ Use typed API for best performance

Performance: ~32ns per mapping (4.6x faster than Runtime!)

What happens at compile-time:

  • Source Generator analyzes UserProfile
  • Generates optimized C# mapping methods
  • Creates HyperMapperGeneratedRegistry with all mappers
  • Zero reflection, zero warm-up time

Two Usage Modes

HyperMapper offers two complementary approaches to object mapping:

Runtime Mode

AutoMapper-compatible dynamic configuration

  • Uses the familiar AutoMapper API
  • Configuration happens at application startup
  • Execution plans compiled to IL at runtime
  • Full support for dynamic scenarios
  • Performance: ~147ns per simple mapping

Best for:

  • Rapid prototyping and development
  • Dynamic type resolution
  • Complex custom converters with runtime dependencies
  • Migration from AutoMapper (zero code changes)

CodeGen Mode

Compile-time code generation via Source Generators

  • Analyzes Profile classes at compile-time
  • Generates optimized C# mapping methods
  • Zero reflection, zero runtime overhead
  • Compile-time error detection
  • Performance: ~32ns per simple mapping (4.6x faster than Runtime!)

Best for:

  • Production applications (recommended)
  • Performance-critical paths
  • AOT/Native compilation scenarios
  • Early error detection

Runtime vs CodeGen Comparison

Aspect Runtime Mode CodeGen Mode
Configuration MapperConfiguration at runtime Profile classes analyzed at compile-time
Performance Fast (~147ns) Ultra-fast (~32ns, 4.6x faster!)
First Call ~1-5ms warm-up ~32ns (no warm-up)
Error Detection Runtime exceptions Compile-time errors
Debugging Expression Trees Plain C# code
AOT/Native Partial support Full support
Dynamic Types Full support Limited (open generics only)
BeforeMap/AfterMap ✅ Supported ❌ Not supported
Custom Converters ✅ Full support ⚠️ Limited support
Migration Effort Zero (100% AutoMapper compatible) Zero (same Profile classes)
Use Case Development, prototyping, dynamic Production, performance-critical

When to Use Each Mode

Use Runtime Mode When:
  1. Rapid Development - Prototyping and iteration speed is priority
  2. Dynamic Type Resolution - Types determined at runtime
  3. Complex Converters - Custom ITypeConverter with runtime dependencies
  4. Lifecycle Hooks - Need BeforeMap/AfterMap functionality
  5. Migration Phase - Migrating from AutoMapper with zero changes
Use CodeGen Mode When:
  1. Production Applications - Strongly recommended for all production deployments
  2. Performance Critical - Up to 4.6x faster than Runtime Mode
  3. AOT/Native Compilation - Using Native AOT or trimming
  4. Compile-Time Safety - Want errors caught at build time
  5. All Mapping Scenarios - CodeGen is now faster in ALL scenarios

💡 Pro Tip: Always use CodeGen Mode in production! With the correct typed API (Map<TSource, TDest>()), CodeGen is 4.6x faster than Runtime and works for all scenarios.


Migration from AutoMapper

HyperMapper is designed to be a 100% API-compatible drop-in replacement for AutoMapper.

Migration Steps

  1. Change the namespace - That's it!
// Before
using AutoMapper;

// After
using HyperMapper;
  1. Update project references - Remove AutoMapper, add HyperMapper

<PackageReference Include="AutoMapper" Version="*" />


<ProjectReference Include="../HyperMapper/src/HyperMapper/HyperMapper.csproj" />
  1. Run and verify - No code changes required!

Automatic Migration Script

# Replace all usings in a directory
find . -name "*.cs" -exec sed -i '' 's/using AutoMapper;/using HyperMapper;/g' {} \;

What's Compatible?

✅ Fully Compatible:

  • IMapper interface with all Map<>() methods
  • Profile with CreateMap<>()
  • ForMember(), ForMember().MapFrom(), ForMember().Ignore()
  • ReverseMap()
  • MapperConfiguration with AddProfile<>()
  • ITypeConverter<TSource, TDest>
  • IValueResolver<TSource, TDest, TMember> with MapFrom<TResolver>() (NEW in v12.0.0)
  • ResolutionContext
  • Collection mapping (List, Array, IEnumerable, Dictionary, etc.)
  • Enum to string conversion
  • Nested object mapping
  • Constructor mapping with ConstructUsing() and ForCtorParam()
  • Value transformations with AddTransform<>()
  • Inheritance mapping with Include() and IncludeBase()
  • Member flattening with IncludeMembers()
  • Conditional mapping with Condition() and PreCondition()
  • Null substitution with NullSubstitute()
  • Path mapping with ForPath()
  • Lifecycle hooks with BeforeMap() and AfterMap()
  • Reference preservation with PreserveReferences()
  • Depth limiting with MaxDepth()
  • Assembly scanning with AddMaps()
  • Mapping order control with SetMappingOrder() (v12.1.0)

⚠️ Behavioral Differences:

  • Performance: HyperMapper is 1.2-5.3x faster
  • First call: HyperMapper has zero warm-up time with CodeGen
  • Memory: HyperMapper uses less memory (up to 30% savings)

🚀 Performance Best Practice:

For maximum performance, use the typed API:

// ✅ RECOMMENDED - Fast Path (4.6x faster)
var result = mapper.Map<Source, Destination>(source);

// ❌ AVOID - Slow Path (uses DynamicInvoke)
var result = mapper.Map<Destination>(source);

The typed API (Map<TSource, TDest>) uses compile-time generated mappers with direct method calls (~32ns). The single-type API (Map<TDest>) requires reflection and DynamicInvoke (~147ns+).

Migration Checklist

  • Replace using AutoMapper; with using HyperMapper; in all .cs files
  • Remove AutoMapper PackageReference from .csproj files
  • Add ProjectReference to HyperMapper in .csproj files
  • Run build to verify compatibility
  • Run tests to validate functionality
  • (Optional) Enable CodeGen Mode for production performance
  • (Optional) Add HyperMapperGeneratedRegistry.Initialize(config) for 2-3x speedup

Entity Framework & Lazy Loading Considerations

⚠️ IMPORTANT: If you're using Entity Framework Core with lazy loading proxies, read this section carefully before migrating to HyperMapper.

Overview

HyperMapper's CodeGen mode generates compiled code at build-time, which fundamentally changes how navigation properties are accessed compared to AutoMapper's runtime reflection. This difference has critical implications when working with Entity Framework Core's lazy loading feature.

Key Insight: AutoMapper can trigger EF lazy loading through reflection, but HyperMapper's compiled code cannot. This means navigation properties that worked automatically with AutoMapper will be null with HyperMapper unless explicitly loaded.

The Issue: Lazy Loading Behavioral Difference

AutoMapper (Runtime Reflection)

  • Uses PropertyInfo.GetValue() to access navigation properties
  • EF Core proxies intercept these reflection calls
  • Lazy loading is automatically triggered
  • Navigation properties are loaded on-demand
  • ✅ Works with lazy loading

HyperMapper (Compiled Code)

  • Generates direct property access: entity.NavigationProperty
  • EF Core proxies cannot intercept compiled code
  • No lazy loading trigger
  • Navigation properties remain null if not pre-loaded
  • ❌ Does not work with lazy loading

Example:

// With AutoMapper (lazy loading works)
CreateMap<Order, OrderDto>()
    .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(
        src => src.Customer.Name  // ✅ Customer is lazy-loaded automatically
    ));

// With HyperMapper (lazy loading does NOT work)
CreateMap<Order, OrderDto>()
    .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(
        src => src.Customer.Name  // ❌ Customer is NULL - NullReferenceException!
    ));

Why This Happens

The difference lies in how the two libraries access entity properties:

// AutoMapper (Runtime)
// 1. PropertyInfo.GetValue(entity, "Customer")
// 2. EF proxy intercepts PropertyInfo.GetValue()
// 3. Lazy loading triggered ✅
// 4. Customer entity loaded from database
// 5. Returns loaded Customer

// HyperMapper (CodeGen)
// 1. Generated code: return entity.Customer.Name;
// 2. Direct property access - no interception possible
// 3. Customer is NULL (not loaded)
// 4. NullReferenceException ❌

EF Core's lazy loading relies on method interception at runtime. Since HyperMapper generates static compiled code, there's no runtime method call to intercept.

Identifying Lazy Loading Dependencies

Before migrating to HyperMapper, identify all places where your code relies on lazy loading:

Strategy 1: Analyze Repository Queries

Look for repository methods that return IQueryable or entities without .Include():

// ❌ PROBLEM: Query without Include
public IQueryable<Order> GetFilteredOrders(OrderFilterDto filter)
{
    return _context.Set<Order>()
        .Where(o => o.OrderDate >= filter.FromDate);
        // Navigation properties (Customer, OrderItems) will be NULL with HyperMapper
}

Strategy 2: Analyze Mapping Profiles

Search for mappings that access navigation properties:

// Look for patterns like this in your Profile classes
CreateMap<Order, OrderDetailDto>()
    .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(
        src => src.Customer.Name  // ← Customer navigation property
        //     ^^^^^^^^^^^^
        //     This MUST be pre-loaded with .Include()
    ))
    .ForMember(dest => dest.ProductName, opt => opt.MapFrom(
        src => src.OrderItems.First().Product.Name
        //     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        //     Nested navigation properties MUST be pre-loaded
    ));

Strategy 3: Grep for Navigation Properties

Use command-line tools to find all navigation property accesses:

# Find all navigation property accesses in mapping profiles
grep -r "src\." --include="*Profile.cs" . | grep -E "\.\w+Navigation|\.\w+\.\w+"

# Find repository methods that might need Include
grep -r "IQueryable<" --include="*Repository.cs" .

Solution: Explicit Eager Loading

The solution is to explicitly load all navigation properties using .Include() and .ThenInclude().

Pattern 1: Simple Include (1 level)

var orders = _context.Orders
    .Include(o => o.Customer)  // Load Customer navigation property
    .ToList();

Pattern 2: Nested Include (multiple levels)

var orders = _context.Orders
    .Include(o => o.Customer)           // Level 1
        .ThenInclude(c => c.Address)    // Level 2
            .ThenInclude(a => a.Country)  // Level 3
    .ToList();

Pattern 3: Multiple Branches from Same Root

var orders = _context.Orders
    .Include(o => o.Customer)
        .ThenInclude(c => c.Address)
    .Include(o => o.Customer)  // Repeat root for different branch
        .ThenInclude(c => c.PreferredPaymentMethod)
    .ToList();

Pattern 4: Collection Navigation Properties

var orders = _context.Orders
    .Include(o => o.OrderItems)           // Collection
        .ThenInclude(item => item.Product)  // Items in collection
    .ToList();

Pattern 5: Collections + Other Properties

var orders = _context.Orders
    .Include(o => o.OrderItems)
        .ThenInclude(item => item.Product)
    .Include(o => o.Customer)
        .ThenInclude(c => c.Address)
    .ToList();

Complete Before/After Example

❌ BEFORE (AutoMapper - relies on lazy loading)

// Repository
public IQueryable<Order> GetFilteredOrders(OrderFilterDto filter)
{
    var query = _context.Set<Order>().AsQueryable();

    if (filter.FromDate.HasValue)
        query = query.Where(o => o.OrderDate >= filter.FromDate.Value);

    return query;  // No .Include() - relies on lazy loading
}

// Mapping Profile
CreateMap<Order, OrderDetailDto>()
    .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(
        src => src.Customer.Name))  // AutoMapper triggers lazy loading
    .ForMember(dest => dest.ShippingAddress, opt => opt.MapFrom(
        src => src.Customer.Address.Street));  // Nested lazy loading

✅ AFTER (HyperMapper - explicit eager loading)

// Repository
public IQueryable<Order> GetFilteredOrders(OrderFilterDto filter)
{
    // Pre-load ALL navigation properties used in mapping
    var query = _context.Set<Order>()
        .Include(o => o.Customer)
            .ThenInclude(c => c.Address)
        .Include(o => o.OrderItems)
            .ThenInclude(item => item.Product)
        .AsQueryable();

    if (filter.FromDate.HasValue)
        query = query.Where(o => o.OrderDate >= filter.FromDate.Value);

    return query;
}

// Mapping Profile (unchanged)
CreateMap<Order, OrderDetailDto>()
    .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(
        src => src.Customer.Name))  // Now works - Customer is pre-loaded
    .ForMember(dest => dest.ShippingAddress, opt => opt.MapFrom(
        src => src.Customer.Address.Street));  // Now works - Address is pre-loaded

Override GetByIdAsync Pattern

If you use a generic repository with GetByIdAsync, you may need to override it in specific repositories:

// Base Repository (no navigation properties)
public class Repository<TEntity> where TEntity : class
{
    protected readonly DbContext _context;

    public virtual async Task<TEntity?> GetByIdAsync(int id)
    {
        return await _context.Set<TEntity>().FindAsync(id);
    }
}

// Specific Repository (with navigation properties)
public class OrderRepository : Repository<Order>, IOrderRepository
{
    public OrderRepository(DbContext context) : base(context) { }

    // Override to add .Include()
    public override async Task<Order?> GetByIdAsync(int id)
    {
        return await _context.Set<Order>()
            .Include(o => o.Customer)
            .Include(o => o.OrderItems)
                .ThenInclude(item => item.Product)
            .FirstOrDefaultAsync(o => o.Id == id);
    }
}

Testing Strategy

Create tests to verify that .Include() statements are working correctly:

Test Pattern 1: Verify Includes Work

[Fact]
public async Task GetFilteredOrders_LoadsAllNavigationProperties()
{
    // Arrange
    await using var context = CreateInMemoryContext();
    SeedCompleteData(context);  // Customer → Address → Order → OrderItems

    var repository = new OrderRepository(context);

    // Act
    var query = repository.GetFilteredOrders(new OrderFilterDto());
    var result = query.ToList();

    // Assert
    var first = result.First();
    Assert.NotNull(first.Customer);  // Verify navigation property loaded
    Assert.NotNull(first.Customer.Address);  // Verify nested property loaded
    Assert.NotNull(first.OrderItems);  // Verify collection loaded
    Assert.NotEmpty(first.OrderItems);  // Verify collection has items
}

Test Pattern 2: Negative Test (without Include)

[Fact]
public async Task Order_WithoutInclude_HasNullNavigationProperties()
{
    // Arrange
    var dbName = $"TestDb_{Guid.NewGuid()}";
    await using var contextForSeeding = CreateInMemoryContextWithName(dbName);
    SeedCompleteData(contextForSeeding);
    await contextForSeeding.SaveChangesAsync();

    // Act - Use NEW context for query WITHOUT Include
    await using var contextForQuerying = CreateInMemoryContextWithName(dbName);
    var query = contextForQuerying.Set<Order>().AsNoTracking();
    var result = query.ToList();

    // Assert - Demonstrates lazy loading does NOT work
    Assert.Null(result.First().Customer);  // NULL because not loaded
}

Test Pattern 3: Integration Test with Mapping

[Fact]
public async Task OrderToDto_WithIncludes_MapsAllProperties()
{
    // Arrange
    await using var context = CreateInMemoryContext();
    SeedCompleteData(context);

    var repository = new OrderRepository(context);
    var mapper = CreateMapper();

    // Act
    var query = repository.GetFilteredOrders(new OrderFilterDto());
    var orders = query.ToList();
    var dto = mapper.Map<OrderDetailDto>(orders.First());

    // Assert - DTO must have ALL nested data
    Assert.Equal("John Doe", dto.CustomerName);
    Assert.Equal("123 Main St", dto.ShippingAddress);
    Assert.NotEmpty(dto.Items);
    Assert.Equal("Widget", dto.Items.First().ProductName);
}

Best Practices

✅ DO:

  1. Add .Include() in repositories, not in service layers
  2. Test with InMemory Database to verify Include statements
  3. Use AsNoTracking() for read-only queries with Include
  4. Document navigation dependencies in code comments
  5. Override GetByIdAsync() when entity-specific includes are needed

❌ DON'T:

  1. Don't rely on lazy loading with HyperMapper CodeGen
  2. Don't add .Include() blindly - only for properties used in mapping
  3. Don't use explicit .Load() - prefer .Include() for performance
  4. Don't mix lazy and eager loading - choose one strategy and stick to it

Performance Tips:

// ✅ GOOD: Include only what you need
.Include(o => o.Customer)
    .ThenInclude(c => c.Address)

// ❌ BAD: Include unnecessary deep/circular data
.Include(o => o.Customer)
    .ThenInclude(c => c.Address)
        .ThenInclude(a => a.City)           // Not used in mapping
            .ThenInclude(city => city.Country)  // Circular/unnecessary

Migration Checklist

When migrating from AutoMapper to HyperMapper with Entity Framework:

  • Identify all repositories that return entities mapped to DTOs
  • Analyze MappingProfile classes for navigation property accesses
  • Add .Include() for all navigation properties referenced in mappings
  • Create tests with InMemory Database to verify Include statements
  • Create negative tests to verify lazy loading doesn't work without Include
  • Run integration tests end-to-end with mapping
  • Verify all API endpoints return complete data

Real-World Example

Scenario: API endpoint returns DTOs with null fields after migrating to HyperMapper

Entity Relationships:

Order → Customer → Address → Country
     → OrderItems → Product → Category

Mapping Profile:

CreateMap<Order, OrderDetailDto>()
    .ForMember(dest => dest.CustomerName, opt => opt.MapFrom(
        src => src.Customer.Name))
    .ForMember(dest => dest.ShippingCountry, opt => opt.MapFrom(
        src => src.Customer.Address.Country.Name))
    .ForMember(dest => dest.Items, opt => opt.MapFrom(
        src => src.OrderItems));

Solution Applied:

public IQueryable<Order> GetFilteredOrders(OrderFilterDto filter)
{
    var query = _context.Set<Order>()
        // Include all navigation properties used in mapping
        .Include(o => o.Customer)
            .ThenInclude(c => c.Address)
                .ThenInclude(a => a.Country)
        .Include(o => o.OrderItems)
            .ThenInclude(item => item.Product)
                .ThenInclude(p => p.Category)
        .AsQueryable();

    // Apply filters...
    if (filter.FromDate.HasValue)
        query = query.Where(o => o.OrderDate >= filter.FromDate.Value);

    return query;
}

Result: ✅ API endpoint works correctly, all DTOs fully populated

Lazy Loading Proxies Compatibility Issue

⚠️ CRITICAL: HyperMapper CodeGen mode is incompatible with Entity Framework Core's .UseLazyLoadingProxies() feature.

The Problem

When using EF Core with .UseLazyLoadingProxies(), Entity Framework creates dynamic proxy classes at runtime that intercept property access to enable lazy loading. HyperMapper's CodeGen mode generates compiled code that cannot properly access properties of these proxy classes, resulting in null values even when navigation properties are explicitly loaded with .Include().

Symptoms:

  • Navigation properties are null in mapped DTOs
  • Problem occurs only with SQL Server or other real databases (not InMemory)
  • .Include() statements appear to be ignored
  • Same code works with AutoMapper

Root Cause:

// With .UseLazyLoadingProxies() enabled:
var entity = await context.Orders.Include(o => o.Customer).FirstAsync();

// EF returns a dynamic proxy class: OrderProxy (inherits from Order)
// entity.Customer is loaded ✅

// HyperMapper CodeGen generates:
// return new OrderDto { CustomerName = source.Customer.Name };

// Problem: The generated code cannot access properties of proxy classes correctly
// Result: source.Customer is null ❌ even though it was loaded
The Solution: Disable Lazy Loading Proxies

Remove .UseLazyLoadingProxies() from your DbContext configuration:

// ❌ BEFORE (doesn't work with HyperMapper CodeGen)
services.AddDbContext<AppDbContext>((serviceProvider, options) =>
{
    options.UseLazyLoadingProxies();  // ← Remove this
    options.UseSqlServer(connectionString);
});

// ✅ AFTER (works with HyperMapper CodeGen)
services.AddDbContext<AppDbContext>((serviceProvider, options) =>
{
    // Lazy loading proxies disabled for HyperMapper CodeGen compatibility
    // All navigation properties must be explicitly loaded with .Include()
    options.UseSqlServer(connectionString);
});

Why This Works:

  • Without lazy loading proxies, EF Core returns concrete entity classes instead of dynamic proxies
  • HyperMapper's generated code can access properties of concrete classes correctly
  • Explicit .Include() ensures all navigation properties are loaded before mapping
  • No performance penalty - explicit loading is actually more efficient than lazy loading
Testing with Production Database

To verify the fix works with your production database, create a diagnostic test:

[Fact(Skip = "Diagnostic - connects to production database")]
public async Task ProductionDatabase_VerifyNavigationProperties()
{
    // Create DbContext WITHOUT lazy loading proxies
    var options = new DbContextOptionsBuilder<AppDbContext>()
        .UseSqlServer("your-production-connection-string")
        // .UseLazyLoadingProxies()  // ← Disabled
        .Options;

    await using var context = new AppDbContext(options);
    var mapper = CreateMapper();

    // Load entities with Include
    var entities = await context.Orders
        .Include(o => o.Customer)
            .ThenInclude(c => c.Address)
        .Take(3)
        .ToListAsync();

    // Map to DTOs
    var dtos = mapper.Map<List<OrderDto>>(entities);

    // Verify navigation properties are populated
    Assert.NotNull(dtos.First().Customer);
    Assert.NotNull(dtos.First().Customer.Address);
}
Migration Checklist

When disabling lazy loading proxies:

  • Remove .UseLazyLoadingProxies() from all DbContext configurations
  • Verify all repository methods use explicit .Include() for navigation properties
  • Add .Include() statements for any missing navigation properties
  • Create integration tests with real database to verify Include statements work
  • Update all environments (Development, Staging, Production)
  • Restart application after deployment to clear EF Core cached plans

Reference: See Real-World Example section above for complete repository patterns with explicit Include.


Runtime Mode Documentation

Runtime Mode uses the AutoMapper-compatible API for dynamic configuration at runtime.

Basic Configuration

var config = new MapperConfiguration(cfg =>
{
    // Add profiles
    cfg.AddProfile<UserProfile>();
    cfg.AddProfile<OrderProfile>();

    // Or scan assemblies
    cfg.AddMaps(Assembly.GetExecutingAssembly());
});

// Validate configuration
config.AssertConfigurationIsValid();

// Create mapper
var mapper = config.CreateMapper();

Profile Creation

public class MappingProfile : Profile
{
    public MappingProfile()
    {
        // Simple mapping
        CreateMap<Source, Destination>();

        // With custom configuration
        CreateMap<Order, OrderDto>()
            .ForMember(d => d.Total, opt => opt.MapFrom(s =>
                s.Items.Sum(i => i.Price)))
            .ForMember(d => d.ItemCount, opt => opt.MapFrom(s =>
                s.Items.Count))
            .ForMember(d => d.InternalCode, opt => opt.Ignore());

        // Bidirectional mapping
        CreateMap<Entity, EntityDto>().ReverseMap();
    }
}

Member Configuration

CreateMap<User, UserDto>()
    // Map from expression
    .ForMember(d => d.FullName, opt => opt.MapFrom(s =>
        $"{s.FirstName} {s.LastName}"))

    // Map from destination-dependent expression
    .ForMember(d => d.UpdatedName, opt => opt.MapFrom((s, d) =>
        d.UpdatedName ?? s.Name))

    // Ignore property
    .ForMember(d => d.InternalId, opt => opt.Ignore())

    // Null substitute
    .ForMember(d => d.Name, opt => opt.NullSubstitute("N/A"))

    // Pre-condition (only map if condition is true)
    .ForMember(d => d.SecretData, opt =>
    {
        opt.PreCondition(s => s.IsAuthorized);
        opt.MapFrom(s => s.SecretData);
    })

    // Post-condition (set value only if condition is true)
    .ForMember(d => d.Status, opt =>
    {
        opt.MapFrom(s => s.Status);
        opt.Condition((s, d, val) => val != null);
    });

Type-Level Configuration

CreateMap<Source, Dest>()
    // Type-level transformations (e.g., trim all strings)
    .AddTransform<string>(s => s?.Trim())

    // Custom converter
    .ConvertUsing(s => new Dest { Value = s.Value * 2 })

    // Or use ITypeConverter
    .ConvertUsing<CustomConverter>()

    // Before/After map hooks
    .BeforeMap((s, d) => { /* pre-processing */ })
    .AfterMap((s, d) => { /* post-processing */ })

    // Max depth for circular references
    .MaxDepth(2)

    // Preserve object references
    .PreserveReferences();

Constructor Mapping

public class Destination
{
    public int Id { get; }
    public string Name { get; }

    public Destination(int id, string name)
    {
        Id = id;
        Name = name;
    }
}

CreateMap<Source, Destination>()
    .ConstructUsing(s => new Destination(s.Id, s.Name))
    // Or map individual constructor parameters
    .ForCtorParam("id", opt => opt.MapFrom(s => s.Identifier))
    .ForCtorParam("name", opt => opt.MapFrom(s => s.FullName));

Inheritance Mapping

CreateMap<Person, PersonDto>()
    .Include<Employee, EmployeeDto>()
    .Include<Customer, CustomerDto>();

CreateMap<Employee, EmployeeDto>()
    .IncludeBase<Person, PersonDto>();

CreateMap<Customer, CustomerDto>()
    .IncludeBase<Person, PersonDto>();

Flattening with IncludeMembers

public class Order
{
    public int Id { get; set; }
    public Address ShippingAddress { get; set; }
}

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
}

public class OrderDto
{
    public int Id { get; set; }
    public string Street { get; set; }  // Flattened from ShippingAddress
    public string City { get; set; }    // Flattened from ShippingAddress
}

CreateMap<Order, OrderDto>()
    .IncludeMembers(s => s.ShippingAddress);

CreateMap<Address, OrderDto>();

Usage Examples

// Simple map
var dto = mapper.Map<UserDto>(user);

// Map with explicit types
var dto = mapper.Map<User, UserDto>(user);

// Map to existing object (update)
var existingDto = new UserDto { Id = 1 };
mapper.Map(user, existingDto);

// Map collections
var dtos = mapper.Map<List<UserDto>>(users);
var dtoArray = mapper.Map<UserDto[]>(users);
var dtoEnumerable = mapper.Map<IEnumerable<UserDto>>(users);

// Map with runtime types
object source = user;
var dto = mapper.Map(source, typeof(User), typeof(UserDto));

See full Runtime Mode example: examples/HyperMapper.Examples.Runtime


CodeGen Mode Documentation

CodeGen Mode uses Roslyn Source Generators to analyze your Profile classes at compile-time and generate optimized C# mapping methods.

How Source Generators Work

During compilation, HyperMapper's Source Generator:

  1. Scans for Profile classes - Finds all classes inheriting from HyperMapper.Profile
  2. Analyzes CreateMap calls - Extracts source and destination types
  3. Parses ForMember configurations - Extracts MapFrom, Ignore, and other member configurations
  4. Generates C# code - Creates static mapper methods with explicit property assignments
  5. Creates a Registry - Generates HyperMapperGeneratedRegistry to register all mappers
┌─────────────────────────────────────────────────────────────────┐
│                      COMPILE-TIME                                │
├─────────────────────────────────────────────────────────────────┤
│  1. Source Generator analyzes your Profile classes               │
│  2. Extracts CreateMap<A,B>() and ForMember() calls             │
│  3. Generates .g.cs files with explicit mapping code            │
│                                                                  │
│     // Auto-generated: UserProfileGeneratedMappers.g.cs         │
│     public static UserDto MapUserToUserDto(User source) {       │
│         return new UserDto {                                     │
│             Id = source.Id,                                      │
│             FullName = $"{source.FirstName} {source.LastName}"   │
│         };                                                       │
│     }                                                            │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────┐
│                        RUNTIME                                   │
├─────────────────────────────────────────────────────────────────┤
│  1. App Start → No reflection needed                             │
│  2. Map() → ~44ns (code already compiled!)                       │
└─────────────────────────────────────────────────────────────────┘

Enabling CodeGen Mode

Step 1: Configure .csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>

    
    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
    <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\Generated</CompilerGeneratedFilesOutputPath>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="../HyperMapper/src/HyperMapper/HyperMapper.csproj"
                      OutputItemType="Analyzer"
                      ReferenceOutputAssembly="true" />
  </ItemGroup>
</Project>
Step 2: Create Profile (Same as Runtime)
using HyperMapper;

public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<User, UserDto>()
            .ForMember(d => d.FullName, opt => opt.MapFrom(s =>
                $"{s.FirstName} {s.LastName}"));
    }
}
Step 3: Register Generated Mappers
using HyperMapper;
using HyperMapper.Generated;

var config = new MapperConfiguration(cfg =>
{
    cfg.AddProfile<UserProfile>();
});

// CRITICAL: Register generated mappers for maximum performance
HyperMapperGeneratedRegistry.Initialize(config);

config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();

Generated Code Structure

For each Profile, the Source Generator creates:

1. Mapper Methods File ({ProfileName}GeneratedMappers.g.cs)
// Auto-generated: UserProfileGeneratedMappers.g.cs
#nullable enable

namespace MyApp.Profiles;

[global::System.CodeDom.Compiler.GeneratedCode("HyperMapper.SourceGenerator", "12.0.0")]
internal static class UserProfileGeneratedMappers
{
    /// <summary>
    /// Maps User to UserDto
    /// </summary>
    [return: global::System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(source))]
    public static UserDto? MapUserToUserDto(User? source)
    {
        if (source is null) return null;

        return new UserDto
        {
            Id = source.Id,
            // MapFrom expression inlined
            FullName = $"{source.FirstName} {source.LastName}",
        };
    }

    /// <summary>
    /// Maps IEnumerable&lt;User&gt; to List&lt;UserDto&gt;
    /// </summary>
    public static List<UserDto> MapUserToUserDtoList(IEnumerable<User>? source)
    {
        if (source is null) return new();

        var sourceCollection = source as ICollection<User> ?? source.ToList();
        var result = new List<UserDto>(sourceCollection.Count);

        foreach (var item in sourceCollection)
        {
            result.Add(MapUserToUserDto(item)!);
        }

        return result;
    }
}
2. Registry File (HyperMapperGeneratedRegistry.g.cs)
// Auto-generated: HyperMapperGeneratedRegistry.g.cs
#nullable enable

namespace HyperMapper.Generated;

[global::System.CodeDom.Compiler.GeneratedCode("HyperMapper.SourceGenerator", "12.0.0")]
public static class HyperMapperGeneratedRegistry
{
    private static bool _initialized;

    public static void Initialize(HyperMapper.MapperConfiguration config)
    {
        if (_initialized) return;
        _initialized = true;

        // User -> UserDto
        config.RegisterGeneratedPlan<User, UserDto>(
            UserProfileGeneratedMappers.MapUserToUserDto);
    }
}

Viewing Generated Code

After building with EmitCompilerGeneratedFiles enabled:

cd obj/Generated/HyperMapper.SourceGenerator/HyperMapper.SourceGenerator.MapperGenerator/

# View generated mappers
cat UserProfileGeneratedMappers.g.cs

# View registry
cat HyperMapperGeneratedRegistry.g.cs

Supported Scenarios

Scenario Support Example
Simple properties Id = source.Id
Nested objects Address = MapAddressToAddressDto(source.Address)
Collections Items = source.Items?.Select(MapItem).ToList()
Enum ↔ String Status = source.Status.ToString()
Nullable conversions Value = source.Value ?? default
ForMember/MapFrom FullName = $"{source.First} {source.Last}"
ForMember/Ignore Property skipped in generation
String interpolation $"{source.Street}, {source.City}"
Arithmetic Total = source.Qty * source.Price
Flattening AddressStreet = source.Address?.Street
Struct mapping Point → PointDto
PreCondition if (source.IsActive) dest.Value = ...
Lambda Converters ConvertUsing(s => new Dest {...}) inlined
Open Generics Box<T> → BoxDto<T>

Compile-Time Diagnostics

Code Severity Description Resolution
LMAP001 Error Destination type lacks parameterless constructor Add parameterless constructor
LMAP002 Warning Unmapped destination property Use ForMember(..., opt => opt.Ignore())
LMAP003 Error Incompatible property types Add explicit MapFrom or converter
LMAP007 Info Struct mapping generated Informational
LMAP008 Info PreCondition compiled at build-time Informational

Best Practices

1. Always Register Generated Mappers
// ✅ Good - explicit registration
HyperMapperGeneratedRegistry.Initialize(config);

// ⚠️ Works but not optimal
// (no explicit registration)
2. Keep Profiles Simple for Best Generation
// ✅ Good - generates clean code
CreateMap<Source, Dest>()
    .ForMember(d => d.Name, opt => opt.MapFrom(s => s.FullName));

// ⚠️ Falls back to runtime - complex converter
CreateMap<Source, Dest>()
    .ConvertUsing(new ComplexConverter());
3. Enable Generated Files During Development
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
  <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>

Troubleshooting

Generated Code Not Being Used

Symptoms: Performance similar to Runtime Mode

Solutions:

  1. Ensure HyperMapperGeneratedRegistry.Initialize(config) is called
  2. Verify Profile inherits from HyperMapper.Profile
  3. Rebuild: dotnet clean && dotnet build
Generated Files Not Visible

Solutions:

  1. Enable EmitCompilerGeneratedFiles in .csproj
  2. Restart IDE
  3. Check obj/Generated/ directory

See full CodeGen Mode example: examples/HyperMapper.Examples.CodeGen


CodeGen Mode Known Limitations

While CodeGen Mode provides significant performance improvements (up to 4.6x faster than Runtime), there are some limitations due to the compile-time nature of Source Generators:

❌ Not Supported in CodeGen Mode

  1. Open Generic CreateMap with Type arguments

    // ❌ NOT SUPPORTED in CodeGen
    CreateMap(typeof(IPagedList<>), typeof(PagedListDto<>))
        .ConvertUsing(typeof(PagedListConverter<,>));
    
    // ✅ WORKAROUND - Use generic constraints
    CreateMap<IPagedList<T>, PagedListDto<T>>()
        .ConvertUsing(s => new PagedListDto<T> { Items = s.Items });
    
  2. BeforeMap/AfterMap hooks (Runtime only)

    // ❌ NOT SUPPORTED in CodeGen
    CreateMap<Source, Dest>()
        .BeforeMap((s, d) => Console.WriteLine("Mapping"))
        .AfterMap((s, d) => d.Validate());
    
    // ✅ WORKAROUND - Use Runtime Mode for these mappings
    

⚠️ Workarounds

For Open Generics: Create specific mappings for each type combination:

// Instead of: CreateMap(typeof(List<>), typeof(ListDto<>))
// Do:
CreateMap<List<User>, ListDto<UserDto>>();
CreateMap<List<Product>, ListDto<ProductDto>>();

✅ Fully Supported Features

All other AutoMapper features work in CodeGen Mode:

  • Class-based ITypeConverter (NEW in v12.0.0)
    // ✅ NOW SUPPORTED in CodeGen!
    CreateMap<Geometry, GeometryPointDto>()
        .ConvertUsing(new GeometryConverter());
    
  • ✅ ForMember with MapFrom (including nested properties)
  • Complex LINQ expressions in MapFrom (NEW in v12.0.0)
    // ✅ NOW SUPPORTED - Where().Select() with type conversion
    .ForMember(d => d.Tags, opt => opt.MapFrom(src =>
        src.PostazioneTags.Where(pt => pt.Tag != null).Select(pt => pt.Tag!)))
    
  • ✅ Condition and PreCondition
  • ✅ ConstructUsing with lambda
  • ✅ ForCtorParam
  • ✅ ForPath
  • ✅ NullSubstitute
  • ✅ Include/IncludeBase (polymorphic mapping)
  • ✅ IncludeMembers (flattening from nested objects)
  • ✅ AddTransform (type-level transformations)
  • ✅ Collections (all types: Array, List, HashSet, Dictionary, etc.)
  • ✅ Flattening (AddressStreet → Address.Street)
  • ✅ ReverseMap
  • ✅ Value converters
  • ✅ Nullable type handling (int? → int with ?? operator)

💡 Best Practice

Start with CodeGen Mode for all mappings, and only fall back to Runtime Mode for the rare cases that require ITypeConverter instances or runtime hooks.


Advanced Features

Type Transformations

Apply transformations to all properties of a specific type:

CreateMap<Source, Dest>()
    // Trim all strings
    .AddTransform<string>(s => s?.Trim())
    // Round all decimals to 2 places
    .AddTransform<decimal>(d => Math.Round(d, 2));

Destination-Dependent Mapping

Map based on both source and destination values:

CreateMap<Source, Dest>()
    .ForMember(d => d.Name, opt => opt.MapFrom((src, dest) =>
        dest.Name ?? src.Name)); // Keep dest.Name if already set

Path Mapping

Map to deep nested properties:

CreateMap<Source, Dest>()
    .ForPath(d => d.Address.Street, opt => opt.MapFrom(s => s.FullAddress));

Circular Reference Handling

CreateMap<Category, CategoryDto>()
    .MaxDepth(3)                // Limit recursion depth
    .PreserveReferences();       // Maintain object references

Assembly Scanning with AutoMap Attribute

[AutoMap(typeof(UserDto))]
public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// In configuration
cfg.AddMaps(Assembly.GetExecutingAssembly());

Value Resolvers (v12.0.0)

Custom value resolvers provide reusable, testable mapping logic for complex member transformations. The IValueResolver interface is 100% compatible with AutoMapper.

Interface Definition
public interface IValueResolver<in TSource, in TDestination, TDestMember>
{
    TDestMember Resolve(TSource source, TDestination destination,
        TDestMember destMember, ResolutionContext context);
}
Basic Usage
// 1. Define a resolver
public class FullNameResolver : IValueResolver<User, UserDto, string>
{
    public string Resolve(User source, UserDto destination,
        string destMember, ResolutionContext context)
    {
        return $"{source.FirstName} {source.LastName}";
    }
}

// 2. Use in Profile
public class UserProfile : Profile
{
    public UserProfile()
    {
        CreateMap<User, UserDto>()
            .ForMember(d => d.FullName, opt => opt.MapFrom<FullNameResolver>());
    }
}
Instance-Based Registration

You can also provide a pre-instantiated resolver:

var resolver = new CurrencyFormatter("USD");
CreateMap<Order, OrderDto>()
    .ForMember(d => d.Total, opt => opt.MapFrom(resolver));
Dependency Injection Support

Use ConstructServicesUsing to integrate with your DI container:

var config = new MapperConfiguration(cfg =>
{
    cfg.ConstructServicesUsing(type => serviceProvider.GetService(type)!);
    cfg.AddProfile<UserProfile>();
});

Now resolvers can have constructor dependencies injected:

public class PricingResolver : IValueResolver<Order, OrderDto, decimal>
{
    private readonly IPricingService _pricing;

    public PricingResolver(IPricingService pricing)
    {
        _pricing = pricing;
    }

    public decimal Resolve(Order source, OrderDto destination,
        decimal destMember, ResolutionContext context)
    {
        return _pricing.Calculate(source.Items);
    }
}
Nested Mapping via Context

Access the mapper through the resolution context to perform nested mappings:

public class CustomerResolver : IValueResolver<Order, OrderDto, CustomerInfo>
{
    public CustomerInfo Resolve(Order source, OrderDto dest,
        CustomerInfo member, ResolutionContext context)
    {
        return context.Mapper.Map<CustomerInfo>(source.Customer);
    }
}
Combining with PreCondition

Value resolvers work seamlessly with PreCondition:

CreateMap<Source, Dest>()
    .ForMember(d => d.Value, opt =>
    {
        opt.PreCondition(src => src.ShouldMap);
        opt.MapFrom<MyValueResolver>();
    });
CodeGen Mode Support

Value resolvers are fully supported in both Runtime and CodeGen modes. In CodeGen mode, the resolver is instantiated via Activator.CreateInstance() and the generated code calls the Resolve method directly.

Note: In CodeGen mode, the ResolutionContext parameter may be null. Resolvers that depend on context.Mapper for nested mappings will fall back to Runtime execution.

Source Generator Enhancements (v12.1.x)

Version 12.1.x introduces several improvements to the Source Generator for better compatibility with real-world codebases.

External Assembly Type Support (v12.0.2)

Types from external assemblies (NuGet packages, referenced projects) are now fully supported in mapping expressions. The Source Generator correctly resolves fully-qualified type names:

// Example: Using EntityFramework types
using Microsoft.EntityFrameworkCore;

public class EntitySource
{
    public EntityState State { get; set; }
}

public class EntityDto
{
    public EntityState MappedState { get; set; }
    public string StateDescription { get; set; }
}

// Profile
CreateMap<EntitySource, EntityDto>()
    .ForMember(d => d.MappedState, opt => opt.MapFrom(s => s.State))
    .ForMember(d => d.StateDescription, opt => opt.MapFrom(s => s.State.ToString()));
Ambiguous Static Class Resolution (v12.1.0)

Common static classes like Path, File, Math, Convert, etc. are automatically qualified to prevent CS0104 ambiguity errors when other libraries define types with the same names:

// These expressions work correctly even if your codebase has a "Path" class
CreateMap<FileSource, FileDto>()
    .ForMember(d => d.Extension, opt => opt.MapFrom(s => Path.GetExtension(s.FilePath)))
    .ForMember(d => d.FileName, opt => opt.MapFrom(s => Path.GetFileName(s.FilePath)))
    .ForMember(d => d.Rounded, opt => opt.MapFrom(s => Math.Round(s.Value, 2)))
    .ForMember(d => d.IntValue, opt => opt.MapFrom(s => Convert.ToInt32(s.StringValue)));

Automatically qualified classes:

  • Pathglobal::System.IO.Path
  • Fileglobal::System.IO.File
  • Directoryglobal::System.IO.Directory
  • Mathglobal::System.Math
  • Convertglobal::System.Convert
  • Encodingglobal::System.Text.Encoding
  • Environmentglobal::System.Environment
Base Class Property Resolution (v12.1.0)

Properties from base classes are now correctly resolved in MapFrom expressions:

public class BaseEntity
{
    public int Id { get; set; }
    public DateTime CreatedAt { get; set; }
}

public class DerivedEntity : BaseEntity
{
    public string Name { get; set; }
}

public class EntityDto
{
    public int EntityId { get; set; }
    public string CreatedDate { get; set; }
    public string Name { get; set; }
}

// Base class properties (Id, CreatedAt) are correctly resolved
CreateMap<DerivedEntity, EntityDto>()
    .ForMember(d => d.EntityId, opt => opt.MapFrom(s => s.Id))
    .ForMember(d => d.CreatedDate, opt => opt.MapFrom(s => s.CreatedAt.ToString("yyyy-MM-dd")));
Standalone Lambda Parameter Support (v12.1.0)

Lambda expressions with standalone parameters (not just property access) are now handled correctly:

CreateMap<Source, Dest>()
    // Ternary expressions with parameter
    .ForMember(d => d.NameOrDefault, opt => opt.MapFrom(s =>
        string.IsNullOrEmpty(s.Name) ? "Default" : s.Name))

    // Null coalescing
    .ForMember(d => d.Value, opt => opt.MapFrom(s => s.NullableInt ?? 0))

    // Null conditional with nested object
    .ForMember(d => d.InnerName, opt => opt.MapFrom(s =>
        s.Inner == null ? "N/A" : s.Inner.Name));

Mapping Order Control (v12.1.0)

Control the sequence in which properties are mapped using SetMappingOrder(). This is essential when destination property setters have side effects or dependencies on other properties.

When to Use SetMappingOrder

Use SetMappingOrder() when:

  • Dependent property setters: One property's setter modifies another property
  • Initialization sequences: Properties must be set in a specific order for validation
  • Side-effect management: Control execution order when setters have observable effects
Execution Order Rules
  1. Properties without SetMappingOrder execute first (default behavior)
  2. Properties with explicit order execute in ascending order (-500 before 0 before 600)
  3. Properties with the same order value maintain ForMember() definition sequence
Example 1: Dependent Property Setters

The primary use case is when a destination property setter has a side effect that modifies another property:

public class Destination
{
    private string one;
    public string One
    {
        get => one;
        set
        {
            one = value;
            Two = value; // Side effect: also sets Two
        }
    }
    public string Two { get; set; }
}

// Configure mapping order to preserve independent values
CreateMap<Source, Destination>()
    .ForMember(d => d.One, opt =>
    {
        opt.MapFrom(s => s.First);
        opt.SetMappingOrder(-500); // Execute first
    })
    .ForMember(d => d.Two, opt =>
    {
        opt.MapFrom(s => s.Second);
        opt.SetMappingOrder(600); // Execute after One, preserving independent value
    });

var result = mapper.Map<Destination>(source);
// result.One = "first"
// result.Two = "second" (not overwritten by One's setter)

Without SetMappingOrder, the mapping order is undefined, and One's setter might overwrite Two's value.

Example 2: Property Accumulation

When a property accumulates or concatenates values:

public class AccumulatingDestination
{
    private string id = "";
    public string ID
    {
        get => id;
        set => id = string.Concat(ID, value); // Accumulates values
    }
}

CreateMap<Source, AccumulatingDestination>()
    .ForMember(d => d.ID, opt =>
    {
        opt.MapFrom(s => s.ClientID);
        opt.SetMappingOrder(-1000); // Map first to establish base value
    });
Example 3: Mixed Ordering

Combining null order, negative values, and positive values:

CreateMap<Source, Destination>()
    .ForMember(d => d.Priority, opt =>
    {
        opt.MapFrom(s => s.PriorityValue);
        // No SetMappingOrder - maps first (null order)
    })
    .ForMember(d => d.Name, opt =>
    {
        opt.MapFrom(s => s.FullName);
        opt.SetMappingOrder(-50); // Maps second
    })
    .ForMember(d => d.Description, opt =>
    {
        opt.MapFrom(s => s.Desc);
        opt.SetMappingOrder(0); // Maps third
    })
    .ForMember(d => d.Status, opt =>
    {
        opt.MapFrom(s => s.CurrentStatus);
        opt.SetMappingOrder(100); // Maps last
    });

// Execution order: Priority (null) → Name (-50) → Description (0) → Status (100)
Compatibility with Other Features

SetMappingOrder works seamlessly with:

  • Conditions: Order is respected, conditions are still evaluated
  • PreConditions: Member positioned in sequence according to order
  • Destination-dependent mappings: MapFrom((src, dest) => ...) respects order
  • Execution plans: Order preserved in compiled expression trees
AutoMapper Compatibility

HyperMapper's SetMappingOrder is 100% compatible with AutoMapper:

  • Same API signature: void SetMappingOrder(int mappingOrder)
  • Same execution order rules (null order first, then ascending)
  • Same behavior with inheritance and other features

Examples

Two complete example applications are available:

Runtime Mode Example

Demonstrates the AutoMapper-compatible Runtime Mode:

  • Basic configuration with MapperConfiguration
  • Profile creation with computed properties
  • Nested object mapping
  • Collection mapping
  • Enum to string conversion
  • Bidirectional mapping with ReverseMap()
  • Performance measurement

Run it:

cd examples/HyperMapper.Examples.Runtime
dotnet run

CodeGen Mode Example

Demonstrates Source Generator CodeGen Mode:

  • .csproj configuration for code generation
  • Struct mapping at compile-time
  • PreCondition compiled to if-statements
  • Viewing generated .g.cs files
  • Compile-time diagnostics
  • Performance comparison with Runtime Mode

Run it:

cd examples/HyperMapper.Examples.CodeGen
dotnet build  # Generate code
dotnet run    # Run example

API Reference

IMapper

Main interface for performing mappings:

public interface IMapper
{
    // Map to new object
    TDestination Map<TDestination>(object source);
    TDestination Map<TSource, TDestination>(TSource source);

    // Map to existing object
    TDestination Map<TSource, TDestination>(TSource source, TDestination destination);

    // Map with runtime types
    object Map(object source, Type sourceType, Type destinationType);
    object Map(object source, object destination, Type sourceType, Type destinationType);
}

Profile

Base class for defining mapping configurations:

public abstract class Profile
{
    protected IMappingExpression<TSource, TDestination> CreateMap<TSource, TDestination>();
    protected IMappingExpressionBase CreateMap(Type sourceType, Type destinationType);
}

MapperConfiguration

Class for configuring the mapper:

public class MapperConfiguration
{
    public MapperConfiguration(Action<IMapperConfigurationExpression> configure);
    public MapperConfiguration(Action<IMapperConfigurationExpression> configure, ILoggerFactory? loggerFactory);

    public void RegisterGeneratedPlan<TSource, TDest>(Func<TSource?, TDest?>? generatedMapper);
    public void AssertConfigurationIsValid();
    public IMapper CreateMapper();
}

IMappingExpression

Fluent API for configuring mappings:

public interface IMappingExpression<TSource, TDestination>
{
    // Member configuration
    IMappingExpression<TSource, TDestination> ForMember<TMember>(
        Expression<Func<TDestination, TMember>> destinationMember,
        Action<IMemberConfigurationExpression<TSource, TDestination, TMember>> memberOptions);

    // Path configuration
    IMappingExpression<TSource, TDestination> ForPath<TMember>(
        Expression<Func<TDestination, TMember>> destinationMember,
        Action<IPathConfigurationExpression<TSource, TDestination, TMember>> memberOptions);

    // Constructor parameter
    IMappingExpression<TSource, TDestination> ForCtorParam(
        string ctorParamName,
        Action<ICtorParamConfigurationExpression<TSource>> paramOptions);

    // Type-level configuration
    IMappingExpression<TSource, TDestination> AddTransform<TValue>(Expression<Func<TValue, TValue>> transformer);
    IMappingExpression<TSource, TDestination> ConvertUsing(Func<TSource, TDestination> converter);
    IMappingExpression<TSource, TDestination> ConvertUsing<TTypeConverter>()
        where TTypeConverter : ITypeConverter<TSource, TDestination>;
    IMappingExpression<TSource, TDestination> ConstructUsing(Func<TSource, TDestination> constructor);

    // Lifecycle hooks
    IMappingExpression<TSource, TDestination> BeforeMap(Action<TSource, TDestination> beforeFunction);
    IMappingExpression<TSource, TDestination> AfterMap(Action<TSource, TDestination> afterFunction);

    // Other
    IMappingExpression<TSource, TDestination> MaxDepth(int depth);
    IMappingExpression<TSource, TDestination> PreserveReferences();
    IMappingExpression<TSource, TDestination> IncludeMembers(params Expression<Func<TSource, object>>[] memberExpressions);
    IMappingExpression<TDestination, TSource> ReverseMap();
}

IMemberConfigurationExpression

Member-level configuration options within ForMember():

public interface IMemberConfigurationExpression<TSource, TDestination, TMember>
{
    // Value mapping
    void MapFrom<TSourceMember>(Expression<Func<TSource, TSourceMember>> sourceMember);
    void MapFrom<TSourceMember>(Func<TSource, TDestination, TSourceMember> resolver);
    void MapFrom(string sourceMemberName);

    // Value Resolvers (v12.0.0)
    void MapFrom<TValueResolver>()
        where TValueResolver : IValueResolver<TSource, TDestination, TMember>;
    void MapFrom(IValueResolver<TSource, TDestination, TMember> resolver);

    // Conditions
    void PreCondition(Func<TSource, bool> condition);
    void Condition(Func<TSource, TDestination, TMember, bool> condition);

    // Ignore and substitute
    void Ignore();
    void NullSubstitute(TMember nullSubstitute);

    // Other
    void UseDestinationValue();
    void SetMappingOrder(int mappingOrder);
}

IValueResolver (v12.0.0)

Interface for custom value resolution:

public interface IValueResolver<in TSource, in TDestination, TDestMember>
{
    TDestMember Resolve(TSource source, TDestination destination,
        TDestMember destMember, ResolutionContext context);
}

Testing and Coverage

HyperMapper is extensively tested to ensure reliability and compatibility:

  • 856 total tests (756 unit + 100 integration)
  • 100% pass rate
  • 82.4% code coverage (90.1% method coverage)

Test categories:

  • Basic mapping (simple objects, collections, nested)
  • Configuration (profiles, validation)
  • Advanced features (ForPath, IncludeMembers, MaxDepth, PreserveReferences)
  • Source Generator (CodeGen compilation, diagnostics)
  • Performance (benchmarks, memory allocation)

Run tests:

cd tests/HyperMapper.Tests
dotnet test

Benchmarks

Comprehensive performance benchmarks comparing HyperMapper (Runtime & CodeGen modes), AutoMapper, and manual mapping across different scenarios.

Environment

  • BenchmarkDotNet: v0.14.0
  • OS: macOS 26.2 (Darwin 25.2.0)
  • CPU: Apple M2 Pro (10 cores)
  • .NET: 8.0.23 (Arm64 RyuJIT AdvSIMD)

1. Collection Mapping Performance

Mapping collections of simple objects (CollectionItemSource → CollectionItemDestination).

Size Method Mean Error Ratio vs Baseline Allocated
Small (10 items)
Manual 301 ns ± 22 ns baseline 616 B
HyperMapper Runtime 597 ns ± 49 ns +98% 744 B
HyperMapper CodeGen 607 ns ± 70 ns +102% 744 B
AutoMapper 601 ns ± 87 ns +100% 808 B
Medium (100 items)
Manual 2,583 ns ± 219 ns baseline 5,656 B
HyperMapper CodeGen 2,100 ns ± 150 ns -19% 5,784 B
HyperMapper Runtime 3,098 ns ± 310 ns +20% 5,784 B
AutoMapper 3,771 ns ± 559 ns +46% 6,992 B
Large (1000 items)
Manual 18,914 ns ± 3,020 ns baseline 56,056 B
HyperMapper CodeGen 29,935 ns ± 2,959 ns +58% 56,184 B
HyperMapper Runtime 34,548 ns ± 15,580 ns +83% 56,184 B
AutoMapper 37,402 ns ± 2,516 ns +98% 64,600 B

Key Insights:

  • CodeGen is 19% faster than manual on medium collections (100 items)
  • CodeGen is 1.6x faster than Runtime on large collections (1000 items)
  • AutoMapper allocates 15% more memory than HyperMapper on large collections
  • CodeGen performance advantage increases with collection size

2. Complex Object Mapping Performance

Mapping complex objects with nullable properties, enums, DateTime, nested objects, and collections.

Scenario Method Mean Error Ratio vs Baseline Allocated
Full Object (all properties set)
Manual 161 ns ± 9 ns baseline 264 B
HyperMapper CodeGen 149 ns ± 10 ns -7% 184 B
HyperMapper Runtime 295 ns ± 25 ns +83% 264 B
AutoMapper 370 ns ± 41 ns +130% 272 B
Sparse Object (with nulls)
Manual 86 ns ± 16 ns baseline 168 B
HyperMapper CodeGen 85 ns ± 6 ns -1% 136 B
HyperMapper Runtime 200 ns ± 18 ns +133% 168 B
AutoMapper 246 ns ± 21 ns +187% 168 B

Key Insights:

  • CodeGen is 7% faster than manual on full complex objects
  • CodeGen allocates 30% less memory than manual mapping (184B vs 264B)
  • CodeGen is 2.5x faster than AutoMapper on complex objects
  • CodeGen is 2x faster than Runtime Mode across all complex scenarios

3. Flattening Performance

Flattening nested objects (ModelObject with Sub, Sub2, SubWithExtraName) to flat DTO.

Method Mean Error Ratio vs Baseline Allocated
Manual 51 ns ± 6 ns baseline 56 B
HyperMapper CodeGen 75 ns ± 7 ns +48% 56 B
HyperMapper Runtime 161 ns ± 36 ns +216% 56 B
AutoMapper 202 ns ± 30 ns +298% 56 B

Key Insights:

  • 🎉 CodeGen is now 2.1x faster than Runtime! (75ns vs 161ns)
  • 49x improvement from API fix (was 1,634ns with wrong API)
  • CodeGen is only 1.5x slower than manual (vs 4.4x for Runtime)
  • Use typed API Map<TSource, TDest>() for best CodeGen performance

4. Deep Nesting Performance

Mapping 10 levels of nested objects (DeepLevel1 → DeepLevel10).

Method Mean Error Ratio vs Baseline Allocated
Manual 230 ns ± 34 ns baseline 328 B
HyperMapper CodeGen 255 ns ± 20 ns +11% 328 B
HyperMapper Runtime 343 ns ± 33 ns +49% 328 B
AutoMapper 365 ns ± 22 ns +59% 328 B

Key Insights:

  • 🎉 CodeGen is now 1.3x faster than Runtime! (255ns vs 343ns)
  • 2.7x improvement from API fix (was 699ns with wrong API)
  • CodeGen is only 1.1x slower than manual (excellent!)
  • Navigation expression optimizations contribute to performance gain

Performance Summary

CodeGen Mode (Recommended for ALL scenarios):

  • Simple mapping - 4.6x faster than Runtime (32ns vs 147ns)
  • Flattening - 2.1x faster than Runtime (75ns vs 161ns)
  • Deep nesting - 1.3x faster than Runtime (255ns vs 343ns)
  • Collection mapping - 1.6x faster than Runtime on large collections
  • Complex objects - 2x faster than Runtime, 7% faster than manual
  • Memory efficiency - 30% less allocation on complex objects

API Usage Best Practices:

// ✅ CORRECT - Fast Path (uses GeneratedMapperCache with typed delegates)
var result = mapper.Map<Source, Destination>(source);  // ~32ns

// ❌ AVOID - Slow Path (uses DynamicInvoke with ~100-300ns overhead)
var result = mapper.Map<Destination>(source);  // ~147ns+ (4.6x slower!)

Overall:

  • HyperMapper CodeGen: Best performance in ALL scenarios - use in production
  • HyperMapper Runtime: Good for development and prototyping
  • AutoMapper: Compatible API, but 1.2-2.5x slower than HyperMapper

HyperMapper vs AutoMapper - Detailed Comparison

For teams considering migration from AutoMapper, here's a direct comparison with HyperMapper modes:

Scenario HyperMapper Runtime HyperMapper CodeGen AutoMapper CodeGen vs AutoMapper
Simple Mapping 147 ns 32 ns 170 ns 5.3x faster
Collection Small (10) 597 ns 607 ns 601 ns +1%
Collection Medium (100) 3,098 ns 2,100 ns 3,771 ns 1.8x faster
Collection Large (1000) 34,548 ns 29,935 ns 37,402 ns 1.2x faster
Complex Object Full 295 ns 149 ns 370 ns 2.5x faster
Complex Object Sparse 200 ns 85 ns 246 ns 2.9x faster
Flattening 161 ns 75 ns 202 ns 2.7x faster
Deep Nesting (10 levels) 343 ns 255 ns 365 ns 1.4x faster

Key Findings:

  • HyperMapper CodeGen wins in 7 out of 8 scenarios
  • Average 2.5x faster than AutoMapper with CodeGen Mode
  • Up to 5.3x faster on simple mappings
  • Use typed API Map<TSource, TDest>() for best performance
  • Same memory footprint or less (30% reduction on complex objects)

Migration Recommendation: HyperMapper is a drop-in replacement for AutoMapper with significantly better performance:

  1. Change using AutoMapper; to using HyperMapper;
  2. Add Source Generator reference for CodeGen Mode
  3. Use typed API: mapper.Map<Source, Dest>(source) instead of mapper.Map<Dest>(source)
  4. Enjoy 2.5x average speed improvement!

Running Benchmarks

cd benchmarks/HyperMapper.Benchmarks
dotnet run -c Release

Run specific benchmark categories:

# Collection benchmarks only
dotnet run -c Release --filter "*Collection*"

# Complex object benchmarks only
dotnet run -c Release --filter "*ComplexObject*"

# Flattening benchmarks only
dotnet run -c Release --filter "*Flattening*"

# Deep nesting benchmarks only
dotnet run -c Release --filter "*DeepNesting*"

Architecture

HyperMapper uses a hybrid architecture combining runtime execution plans with optional compile-time code generation:

Runtime Path

  1. ConfigurationMapperConfiguration analyzes Profile classes
  2. Plan BuildingExecutionPlanBuilder compiles Expression Trees to IL
  3. Execution → Generic static cache provides fast typed delegates
  4. Performance → ~147ns per simple mapping

CodeGen Path

  1. Compile-Time → Source Generator analyzes Profile classes
  2. Generation → Creates optimized C# mapping methods
  3. RegistrationHyperMapperGeneratedRegistry.Initialize()
  4. Execution → Direct method calls via GeneratedMapperCache<TSource, TDest>, zero reflection
  5. Performance → ~32ns per simple mapping (4.6x faster than Runtime!)

Key Components

  • Mapper - Main IMapper implementation
  • TypeMap - Metadata for source→destination mappings
  • ExecutionPlanBuilder - Compiles Expression Trees to IL
  • MappingCodeBuilder - Generates C# code from TypeMaps
  • MapperGenerator - Roslyn IIncrementalGenerator implementation

License

Specify your license here


Contributing

Contributions are welcome! Please feel free to submit issues or pull requests.


Support

For questions, issues, or feature requests, please open an issue on GitHub.


Made with ❤️ for high-performance object mapping

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
12.2.1 79 2/3/2026
12.2.0 79 2/2/2026
12.1.2 97 2/2/2026
12.0.1 91 2/1/2026
12.0.0 86 2/1/2026
11.0.1 90 2/1/2026
11.0.0 87 2/1/2026