Mapify.NET 1.5.4

There is a newer version of this package available.
See the version list below for details.
dotnet add package Mapify.NET --version 1.5.4
                    
NuGet\Install-Package Mapify.NET -Version 1.5.4
                    
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="Mapify.NET" Version="1.5.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Mapify.NET" Version="1.5.4" />
                    
Directory.Packages.props
<PackageReference Include="Mapify.NET" />
                    
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 Mapify.NET --version 1.5.4
                    
#r "nuget: Mapify.NET, 1.5.4"
                    
#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 Mapify.NET@1.5.4
                    
#: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=Mapify.NET&version=1.5.4
                    
Install as a Cake Addin
#tool nuget:?package=Mapify.NET&version=1.5.4
                    
Install as a Cake Tool

Mapify.NET

Tests Build

Mapify is a lightweight .NET library for creating static mapping expressions for C# objects. It bridges the gap between in-memory object mapping and LINQ projections (e.g., Entity Framework), allowing you to reuse the same mapping logic consistently across your application.

Features ✨

  • Zero Boilerplate: Automatically maps properties with compatible names and types.
  • Entity Framework Compatible: Generates expression trees compatible with IQueryable projections.
  • Safe: Handles null checks and type conversions automatically.
  • Flexible: Supports explicit overrides and partial mappings.
  • Performance: Caches compiled delegates for in-memory mapping.

Installation 📦

Install via NuGet:

dotnet add package Mapify.NET

Supported Frameworks 📋

  • .NET 8.0, 9.0, 10.0
  • .NET Standard 2.0, 2.1
  • .NET Framework 4.6.2+

Getting Started 🚀

1. Define your Classes

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

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

public class Person {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Address MainAddress { get; set; }
    public ICollection<Address> Addresses { get; set; }
}

public class PersonDto {
    public string Name { get; set; }
    public AddressDto MainAddress { get; set; }
    public ICollection<AddressDto> Addresses { get; set; }
}

The recommended approach is profile + instance mapper (IMapify). It keeps mapping configuration explicit and is easier to test.

using Mapify.NET;
using Microsoft.Extensions.DependencyInjection;

public class PersonProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<Person, PersonDto>();
    }
}

// Use UseMap<TSource, TTarget>(sourceMember) to explicitly mark that
// a property should be mapped via an existing registered map.
public class QueryProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<Address, AddressDto>();

        CreateMap<Person, PersonDto>(p => new PersonDto {
            Name = p.FirstName + " " + p.LastName,
            MainAddress = UseMap<Address, AddressDto>(p.MainAddress)
        });
    }
}

// Named maps: same source/target pair, different map names.
public class PersonValueProfile : MapifyProfile {
    protected override void Configure() {
        // default map for Person -> string
        CreateMap<Person, string>(x => x.FirstName);

        // named maps for the same Person -> string type pair
        CreateMap<Person, string>("FullName", x => x.FirstName + " " + x.LastName);
        CreateMap<Person, string>("Initials", x => x.FirstName.Substring(0, 1) + x.LastName.Substring(0, 1));
    }
}

// UseMap also supports named maps in profile initializers.
public class StudentProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<Student, StudentDto>("Raw", x => new StudentDto { Name = x.Name });
        CreateMap<Student, StudentDto>("Upper", x => new StudentDto { Name = x.Name.ToUpper() });

        CreateMap<Classroom, ClassroomDto>(x => new ClassroomDto {
            Students = UseMap<IEnumerable<Student>, IEnumerable<StudentDto>>("Upper", x.Students)
        });
    }
}

// If source and destination property names differ, pass the source explicitly.
public class NumberProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<NumberSource, NumberDto>();

        CreateMap<Order, OrderDto>(x => new OrderDto {
            // Order.SourceNumber -> OrderDto.Number
            Number = UseMap<NumberSource, NumberDto>(x.SourceNumber)
        });
    }
}

var services = new ServiceCollection();

// Scans the given assemblies for all IMapifyProfile implementations,
// registers them, and registers IMapify as singleton.
services.AddMapify(typeof(PersonProfile).Assembly);

// Scans the given assemblies for all IMapifyProfile implementations,
// registers them, and registers IMapify as Scoped.
services.AddMapify(ServiceLifetime.Scoped, typeof(PersonProfile).Assembly);

// Manually add profiles of the given assembly
// registers IMapify as Scoped without adding profiles
services.AddMapifyProfiles(typeof(PersonProfile).Assembly);
services.AddMapify(ServiceLifetime.Scoped);

// Register a specific profile manually
services.AddMapifyProfile<PersonProfile>();
services.AddMapify(ServiceLifetime.Scoped);

// Named mapper with isolated profile set
services.AddMapifyProfile<PersonProfile>("queries");
services.AddMapifyNamed("queries", ServiceLifetime.Transient);

var provider = services.BuildServiceProvider();
var defaultMapper = provider.GetRequiredService<IMapify>();
var queryMapper = provider.GetMapify("queries");

CreateMap<TSource, TTarget>(...) inside MapifyProfile is registration-only. Map building is deferred until all profiles are registered, so unordered registrations are supported.

When you need explicit nested map usage in a profile initializer, use UseMap<TSource, TTarget>(x.SourceMember). During build, Mapify resolves the dependency to the registered map (including nullable variants).

To force a specific named map, use UseMap<TSource, TTarget>("Name", x.SourceMember).

You can also chain LINQ operators after UseMap, for example:

CreateMap<Person, PersonDto>(x => new PersonDto {
    Addresses = UseMap<IEnumerable<Address>, IEnumerable<AddressDto>>(x.Addresses)
        .OrderBy(dto => dto.StreetName)
});

UseMap also supports arrays and enumerable types. If a map exists for element types (TSrc -> TDest), you can use it for collection shapes like TSrc[] -> TDest[] and IEnumerable<TSrc> -> IEnumerable<TDest>.

This also applies to named maps: if UseMap is called with a name for a collection shape, Mapify resolves the named map for the individual element types.

CreateMap<Address, AddressDto>("Postal", x => new AddressDto {
    StreetName = x.StreetName
});

CreateMap<Person, PersonDto>(x => new PersonDto {
    Addresses = UseMap<IEnumerable<Address>, IEnumerable<AddressDto>>("Postal", x.Addresses)
        .OrderBy(dto => dto.StreetName)
});

In this example, Mapify applies the named element map Address -> AddressDto with name "Postal" for each item.

3. Use the instance mapper in-memory

var mapper = provider.GetRequiredService<IMapify>();

var dto = mapper.Map<Person, PersonDto>(person);

// named map execution
var fullName = mapper.Map<Person, string>(person, "FullName");

var existing = new PersonDto();
mapper.Map(person, existing);

// named map-to-existing execution
mapper.Map(person, existing, "SomeNamedMap");

4. Use the instance mapper with Entity Framework (IQueryable)

IMapify.GetMap<TSource, TTarget>() returns the expression for projections.

You can also resolve a specific named map with IMapify.GetMap<TSource, TTarget>("Name").

UseMap works with expressions too (not only direct properties). For example, you can filter children before mapping:

public class StudentProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<Student, StudentDto>();

        CreateMap<Classroom, ClassroomDto>(x => new ClassroomDto {
            Students = UseMap<IEnumerable<Student>, IEnumerable<StudentDto>>(
                x.Students.Where(s => s.Name != null)
            )
        });
    }
}
public async Task<IEnumerable<PersonDto>> GetPersonDtosAsync(int skip, int take, CancellationToken cancellationToken = default) {
    var mapExpr = _mapify.GetMap<Person, PersonDto>();

    return await _dbContext.Persons
        .Select(mapExpr)
        .OrderBy(x => x.Name)
        .Skip(skip)
        .Take(take)
        .ToArrayAsync(cancellationToken);
}

The same pattern works for named maps as well:

Students = UseMap<IEnumerable<Student>, IEnumerable<StudentDto>>(
    "Upper",
    x.Students.Where(s => s.Name != null)
)

UseMap can also be used inside calculations. For example, map measurements first, then take the maximum Fahrenheit value and convert it to Celsius:

public class MeasurementProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<Measurement, MeasurementDto>();

        CreateMap<WeatherSample, WeatherSampleDto>(x => new WeatherSampleDto {
            MaxTemperatureCelsius = (
                UseMap<IEnumerable<Measurement>, IEnumerable<MeasurementDto>>(x.Measurements)
                    .OrderBy(m => m.Fahrenheit)
                    .Max(m => m.Fahrenheit)
                - 32m) * 5m / 9m
        });
    }
}

Chaining also works for named maps:

Addresses = UseMap<IEnumerable<Address>, IEnumerable<AddressDto>>("Postal", x.Addresses)
    .OrderBy(dto => dto.StreetName)

For EF/EF Core projections, if you apply ordering or other sequence operators after UseMap, materialize the sequence for stable provider translation:

Addresses = UseMap<IEnumerable<Address>, IEnumerable<AddressDto>>(x.Addresses)
    .OrderBy(dto => dto.StreetName)
    .ToList()

Detailed Functionality 📚

Implicit Mappings

When CreateMap<TSource, TTarget>() is called, Mapify automatically generates bindings for properties where:

  1. Names Match: Source and Destination property names are identical.

  2. Types are Compatible:

    • Exact match.
    • Target is assignable from Source.
    • Nullable Handling:
      • TT? (Implicit cast)
      • T?T (Uses source value if not null, otherwise default(T))

    If a map already exists for a same-name property type pair (e.g. Address -> AddressDto), Mapify uses that map implicitly before falling back to direct assignment.

Named Mappings

Named mappings let you register multiple mappings for the same source/target type pair.

public class PersonValueProfile : MapifyProfile {
    protected override void Configure() {
        CreateMap<Person, string>("FullName", x => x.FirstName + " " + x.LastName);
        CreateMap<Person, string>("Initials", x => x.FirstName.Substring(0, 1) + x.LastName.Substring(0, 1));
    }
}

var fullName = mapify.Map<Person, string>(person, "FullName");
var initials = mapify.Map<Person, string>(person, "Initials");

Rules:

  • A default map and named maps can coexist for the same TSource -> TTarget.
  • Each (TSource, TTarget, Name) combination must be unique.
  • UseMap can target named maps via UseMap<TSource, TTarget>("Name", sourceExpression).

Static API (advanced scenarios)

The static Mapper API is still fully supported, but typically used in advanced scenarios.

Static map declarations
using Mapify.NET;
using System.Linq.Expressions;

public static class PersonMappings {
    public static readonly Expression<Func<Person, PersonDto>> PersonToPersonDto =
        Mapper.CreateMap<Person, PersonDto>(p => new PersonDto {
            Name = $"{p.FirstName} {p.LastName}",
            MainAddress = AddressMappings.AddressToAddressDto.Invoke(p.MainAddress)
        });
}

public static class AddressMappings {
    public static readonly Expression<Func<Address, AddressDto>> AddressToAddressDto =
        Mapper.CreateMap<Address, AddressDto>();
}
Static API with Entity Framework + LINQKit

If your static expressions use .Invoke(...), combine with LINQKit and .AsExpandable().

public async Task<IEnumerable<PersonDto>> GetPersonDtosAsync(int skip, int take, CancellationToken cancellationToken = default) {
    return await _dbContext.Persons
        .AsExpandable()
        .Select(PersonMappings.PersonToPersonDto)
        .OrderBy(x => x.Name)
        .Skip(skip)
        .Take(take)
        .ToArrayAsync(cancellationToken);
}

AsExpandable() is the key piece that lets EF translate expressions that use .Invoke().

Use the package that matches your scenario:

  • LinqKit.Core: expression composition utilities (PredicateBuilder, Invoke, Expand) without EF integration.
  • LinqKit or LinqKit.EntityFramework: for Entity Framework 6.x.
  • LinqKit.Microsoft.EntityFrameworkCore: for Entity Framework Core.

For EF Core, the package ID stays the same (LinqKit.Microsoft.EntityFrameworkCore), but the major version should match EF Core major.

Global Configuration & Static Maps

You can register mappings globally to use the static Mapper.Map convenience methods.

// Register a map globally
Mapper.AddMap(PersonMappings.PersonToPersonDto);

// Or create and add in one step
Mapper.CreateAndAddMap<Person, PersonDto>(p => new PersonDto { ... });

// Use the global map anywhere
var dto = Mapper.Map<Person, PersonDto>(person);

Value Mappings (non-initializer)

Mapify also supports mappings where the expression returns a value directly (not new TTarget { ... }).

Mapper.AddMap<Person, string>(x => x.FirstName);
Mapper.AddMap<SourceStatus, TargetStatus>(x => x == SourceStatus.Active ? TargetStatus.Enabled : TargetStatus.Disabled);

var name = Mapper.Map<Person, string>(person);
var status = Mapper.Map<SourceStatus, TargetStatus>(SourceStatus.Active);

Note: value mappings are supported for Map(source) only. Map(source, target) requires an object-initializer mapping.

Explicit Overrides & Coalescing

You can provide a partial initializer to override specific properties. Mapify also rewrites null-coalescing operators (??) to conditional expressions (x != null ? x : y) to ensure compatibility with all LINQ providers (some EF versions struggle with ??).

Mapper.CreateMap<Person, PersonDto>(p => new PersonDto {
    // Explicit override
    Name = p.FirstName + " " + p.LastName,
    
    // Coalescing is rewritten for EF compatibility
    Region = p.Region ?? "Unknown" 
});

Caching & Performance ⚡

  • Compiled Delegates: Accessors are compiled to delegates.
  • Strategy:
    • Extension Method (.Map()): Caches the compiled delegate for that specific expression instance.
    • Static Mapper.Map: Uses a global cache.
      • Priority: Explicitly added maps (AddMap) take precedence over implicitly generated ones.
      • Auto-Cache: If you use Mapper.Map with useDefaultMapIfTypeMapIsMissing: true before adding a custom map, a default map is generated and cached. However, calling AddMap later will overwrite this cache with your custom definition, so you can safely upgrade from default to custom maps at runtime.

Advanced Usage 🛠️

For advanced scenarios, Mapify exposes several lower-level methods.

Compiling Mappers Manually

If you need high-performance bulk mapping to existing objects and want to manage the delegate lifecycle yourself (referencing Action<TSource, TTarget>), you can use CompileMapper.

// Get the map expression
var mapExpr = PersonMappings.PersonToPersonDto;

// Compile to an Action<Person, PersonDto>
Action<Person, PersonDto> mapAction = Mapper.CompileMapper(mapExpr);

// Use it in a hot loop (zero dictionary lookups)
var target = new PersonDto();
foreach (var item in largeCollection) {
    mapAction(item, target);
    // ...
}

Retrieving Maps

You can retrieve registered maps using GetMap. This is useful in generic or dynamic contexts where you don't have direct access to the static field. GetMap returns null if no map exists and default-map fallback is disabled. If you want throwing behavior, use GetRequiredMap.

// Retrieve a registered map (or null)
var mapExpr = Mapper.GetMap<Person, PersonDto>();

// Or retrieve with fallback to default map generation
var fallbackMapExpr = Mapper.GetMap<Person, PersonDto>(useDefaultMapIfTypeMapIsMissing: true);

// Throw if the map is missing
var requiredMapExpr = Mapper.GetRequiredMap<Person, PersonDto>();

Strict Mode Configuration

By default, strict mode is enabled for global maps, meaning Mapper.Map<S, T>(src) throws if no map is registered. You can disable this to allow automatic fallback to default maps globally, though explicit registration is recommended for performance and control.

// Allow implicit generation of default maps if no custom map is found
Mapper.UseDefaultMapIfTypeMapIsMissing(true);

Contributing 🤝

Contributions are welcome! Please feel free to submit a Pull Request.

License 📄

This project is licensed under the MIT License - see the LICENSE file for details.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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 is compatible.  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 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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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
3.1.8 94 5/31/2026
3.1.7 145 5/4/2026
3.1.6 93 5/3/2026
3.1.5 163 4/20/2026
3.1.4 95 4/20/2026
3.1.3 105 4/7/2026
3.1.2 93 4/7/2026
3.1.1 93 4/7/2026
3.1.0 94 4/7/2026
3.0.0 117 3/24/2026
2.3.2 105 3/18/2026
2.3.1 113 3/16/2026
2.3.0 263 3/4/2026
2.2.0 107 2/27/2026
2.1.0 104 2/27/2026
2.0.1 103 2/27/2026
2.0.0 106 2/26/2026
1.5.6 101 2/26/2026
1.5.5 102 2/25/2026
1.5.4 97 2/25/2026
Loading failed