NuGet

eQuantic Mapper

Object mapping utilities and extensions

Version: 2.0.0

Downloads: 15K+

Status: stable

Category: utilities

Installation

dotnet add package eQuantic.Mapper

Overview

eQuantic.Mapper is a high-performance, compile-time object mapping library for .NET that eliminates reflection overhead by generating mapping code at build time using source generators. It provides powerful features for complex property mappings, aggregations, conditional mapping, and bidirectional transformations with excellent performance characteristics.

Unlike traditional mappers that rely on runtime reflection, eQuantic.Mapper uses Roslyn analyzers to generate optimized mapping code during compilation, resulting in zero reflection overhead and superior performance. The library supports advanced scenarios including property aggregation, conditional mapping based on runtime conditions, context-aware mappings, and bidirectional mappings.

The library is designed for enterprise applications where performance, type safety, and maintainability are critical. It integrates seamlessly with dependency injection containers and provides rich customization options through attributes and partial classes, enabling developers to create sophisticated mapping scenarios while maintaining clean, readable code.

Quick Start

1. Installation and Setup

Install both the core mapper package and the source generator to enable compile-time code generation.

// Install via Package Manager Console
Install-Package eQuantic.Mapper
Install-Package eQuantic.Mapper.Generator

// Or via .NET CLI
dotnet add package eQuantic.Mapper
dotnet add package eQuantic.Mapper.Generator

// Basic using statements
using eQuantic.Mapper;
using eQuantic.Mapper.Attributes;

2. Define Your Models

Create your source and destination models. Use attributes to configure advanced mapping scenarios like property aggregation and conditional mapping.

// Source model
public class UserSource
{
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
    public int Age { get; set; };
    public decimal Salary { get; set; };
    public decimal Bonus { get; set; };
    public bool IsActive { get; set; };
}

// Destination model with aggregation
public class UserDestination
{
    // Property aggregation
    [MapFrom(typeof(UserSource), 
             new[] { nameof(UserSource.FirstName), nameof(UserSource.LastName) }, 
             MapperPropertyAggregation.ConcatenateWithSpace)]
    public string FullName { get; set; } = string.Empty;

    // Numeric aggregation
    [MapFrom(typeof(UserSource), 
             new[] { nameof(UserSource.Salary), nameof(UserSource.Bonus) }, 
             MapperPropertyAggregation.Sum)]
    public decimal TotalIncome { get; set; };

    // Conditional mapping
    [MapFrom(typeof(UserSource), nameof(UserSource.Age))]
    [MapWhen(nameof(UserSource.IsActive))]
    public int? DisplayAge { get; set; };

    // Auto-mapped properties (by name)
    public int Age { get; set; }
}

3. Create Your Mapper

Define a partial class with the Mapper attribute. The source generator will create the implementation automatically.

// Auto-generated mapper
[Mapper(typeof(UserSource), typeof(UserDestination))]
public partial class UserMapper : IMapper
{
    // Optional: Add custom logic
    partial void AfterConstructor()
    {
        OnBeforeMap += (sender, args) =>
        {
            // Pre-processing logic
            Console.WriteLine($"Mapping user: {args.Source.FirstName}");
        };

        OnAfterMap += (sender, args) =>
        {
            // Post-processing logic
            if (args.Destination.Age < 18)
            {
                args.Destination.FullName = $"Minor: {args.Destination.FullName}";
            }
        };
    }
}

4. Configure Dependency Injection

Register all mappers in your DI container using the provided extension method.

var builder = WebApplication.CreateBuilder(args);

// Register all mappers automatically
builder.Services.AddMappers();

var app = builder.Build();

// Use in controllers or services
app.MapGet("/users/{id}", async (int id, IMapperFactory mapperFactory) =>
{
    var mapper = mapperFactory.GetMapper<UserSource, UserDestination>()!;
    var user = await GetUserAsync(id); // Your data access logic
    return mapper.Map(user);
});

app.Run();

5. Advanced Features Usage

Leverage advanced features like bidirectional mapping, context-aware mapping, and conditional logic.

// Bidirectional mapping
[Mapper(typeof(UserSource), typeof(UserDestination), MapperDirection.Bidirectional)]
public partial class BidirectionalUserMapper : IMapper { }

// Context-aware mapping
public class MappingContext
{
    public bool IncludeSensitiveData { get; set; }
    public string UserRole { get; set; } = "User";
}

[Mapper(typeof(UserSource), typeof(UserDestination), typeof(MappingContext))]
public partial class ContextUserMapper : IMapper<UserSource, UserDestination, MappingContext>
{
    partial void AfterConstructor()
    {
        OnAfterMap += (sender, args) =>
        {
            if (!Context?.IncludeSensitiveData == true)
            {
                args.Destination.Salary = 0;
            }
        };
    }
}

// Usage
var contextMapper = mapperFactory.GetMapper<UserSource, UserDestination, MappingContext>()!;
contextMapper.Context = new MappingContext { IncludeSensitiveData = false, UserRole = "User" };
var result = contextMapper.Map(userSource);

API Reference

attribute

MapperAttribute

Defines a mapper class for transforming between two types, with optional context and direction support.

Example
[Mapper(typeof(UserSource), typeof(UserDestination))]
public partial class UserMapper : IMapper
{
    // Auto-generated implementation
}

// With context
[Mapper(typeof(UserSource), typeof(UserDestination), typeof(MappingContext))]
public partial class ContextUserMapper : IMapper<UserSource, UserDestination, MappingContext> { }

// Bidirectional
[Mapper(typeof(UserSource), typeof(UserDestination), MapperDirection.Bidirectional)]
public partial class BiMapper : IMapper { }
attribute

MapFromAttribute

Maps a destination property from one or more source properties with optional aggregation.

Example
// Simple mapping
[MapFrom(typeof(UserSource), nameof(UserSource.FirstName))]
public string Name { get; set; }

// Property aggregation
[MapFrom(typeof(UserSource), 
         new[] { nameof(UserSource.FirstName), nameof(UserSource.LastName) },
         MapperPropertyAggregation.ConcatenateWithSpace)]
public string FullName { get; set; }

// Numeric aggregation
[MapFrom(typeof(UserSource),
         new[] { nameof(UserSource.Salary), nameof(UserSource.Bonus) },
         MapperPropertyAggregation.Sum)]
public decimal TotalIncome { get; set; }
attribute

MapWhenAttribute

Conditionally maps a property based on a boolean property or C# expression.

Example
// Simple boolean condition
[MapFrom(typeof(UserSource), nameof(UserSource.Email))]
[MapWhen(nameof(UserSource.IsEmailVisible))]
public string? PublicEmail { get; set; }

// Expression condition
[MapFrom(typeof(UserSource), nameof(UserSource.Age))]
[MapWhen("source.Age >= 18", true)]
public int? DisplayAge { get; set; }

// Context-aware condition
[MapFrom(typeof(UserSource), nameof(UserSource.Salary))]
[MapWhen("Context?.IncludeSensitiveData == true", true)]
public decimal? Salary { get; set; }
interface

IMapper<TSource, TDestination>

Basic mapper interface for transforming from source to destination type.

Example
public class UserMapper : IMapper<UserSource, UserDestination>
{
    public UserDestination? Map(UserSource? source)
    {
        if (source == null) return null;
        return new UserDestination
        {
            FirstName = source.FirstName,
            LastName = source.LastName
        };
    }
    
    public UserDestination? Map(UserSource? source, UserDestination? destination)
    {
        // Map to existing instance
    }
}
interface

IMapper<TSource, TDestination, TContext>

Context-aware mapper interface with support for mapping context.

Example
public class ContextUserMapper : IMapper<UserSource, UserDestination, MappingContext>
{
    public MappingContext? Context { get; set; }
    
    public UserDestination? Map(UserSource? source)
    {
        if (source == null) return null;
        var result = new UserDestination();
        
        if (Context?.IncludeSensitiveData == true)
        {
            result.Salary = source.Salary;
        }
        
        return result;
    }
}
interface

IMapperFactory

Factory interface for creating and retrieving mapper instances.

Example
// Dependency injection usage
public class UserController : ControllerBase
{
    private readonly IMapperFactory _mapperFactory;
    
    public UserController(IMapperFactory mapperFactory)
    {
        _mapperFactory = mapperFactory;
    }
    
    [HttpGet("{id}")]
    public async Task<UserDto> GetUser(int id)
    {
        var mapper = _mapperFactory.GetMapper<User, UserDto>()!;
        var user = await _userService.GetByIdAsync(id);
        return mapper.Map(user);
    }
}
enum

MapperDirection

Specifies the direction of mapping for bidirectional mappers.

Example
// Forward only (default)
[Mapper(typeof(UserSource), typeof(UserDestination))]
public partial class ForwardMapper : IMapper { }

// Reverse only
[Mapper(typeof(UserSource), typeof(UserDestination), MapperDirection.Reverse)]
public partial class ReverseMapper : IMapper { }

// Bidirectional
[Mapper(typeof(UserSource), typeof(UserDestination), MapperDirection.Bidirectional)]
public partial class BiMapper : IMapper { }
enum

MapperPropertyAggregation

Defines how multiple source properties are combined into a single destination property.

Example
// String concatenation
[MapFrom(typeof(PersonSource),
         new[] { nameof(PersonSource.FirstName), nameof(PersonSource.LastName) },
         MapperPropertyAggregation.ConcatenateWithSpace)]
public string FullName { get; set; }

// Numeric operations
[MapFrom(typeof(PersonSource),
         new[] { nameof(PersonSource.Salary), nameof(PersonSource.Bonus) },
         MapperPropertyAggregation.Sum)]
public decimal TotalIncome { get; set; }

[MapFrom(typeof(PersonSource),
         new[] { nameof(PersonSource.Score1), nameof(PersonSource.Score2) },
         MapperPropertyAggregation.Average)]
public decimal AverageScore { get; set; }

Key Features

Zero Reflection with Source Generation

Uses Roslyn analyzers to generate highly optimized mapping code at compile time, completely eliminating reflection overh...

Property Aggregation System

Combines multiple source properties into single destination properties using various aggregation types including concate...

Conditional Mapping with MapWhen

Maps properties only when specific conditions are met using the MapWhenAttribute. Supports simple boolean property condi...

Bidirectional Mapping Support

Generate mappers that work in both directions (Forward, Reverse, or Bidirectional) with a single class definition using ...

Context-Aware Mapping

Support for context objects that influence mapping behavior, enabling scenarios like user role-based field mapping, tena...

Dependency Injection Integration

Built-in support for dependency injection with IMapperFactory for creating and managing mapper instances, automatic regi...

Package Info

Version
2.0.0
Downloads
15K+
Status
stable
Category
utilities
eQuantic Mapper - Documentation | eQuantic