eQuantic.Mapper
1.8.0
See the version list below for details.
dotnet add package eQuantic.Mapper --version 1.8.0
NuGet\Install-Package eQuantic.Mapper -Version 1.8.0
<PackageReference Include="eQuantic.Mapper" Version="1.8.0" />
<PackageVersion Include="eQuantic.Mapper" Version="1.8.0" />
<PackageReference Include="eQuantic.Mapper" />
paket add eQuantic.Mapper --version 1.8.0
#r "nuget: eQuantic.Mapper, 1.8.0"
#:package eQuantic.Mapper@1.8.0
#addin nuget:?package=eQuantic.Mapper&version=1.8.0
#tool nuget:?package=eQuantic.Mapper&version=1.8.0
🎯 eQuantic.Mapper
High-performance object mapping library for .NET with source generation and advanced aggregation features.
The eQuantic Mapper is a powerful, compile-time object mapping library that eliminates reflection overhead by generating mapping code at build time. It supports complex property mappings, aggregations, and custom transformations with excellent performance characteristics.
🚀 Features
- ✨ Zero Reflection - All mappings are generated at compile time
- 🔄 Source Generation - Uses Roslyn analyzers for code generation
- 📊 Property Aggregation - Combine multiple source properties into single destination properties
- 🎯 Type Safety - Full compile-time type checking
- 🔧 Customizable - Easy to extend and customize mapping behavior
- 📝 Rich Attributes - Declarative mapping configuration
- ⚡ High Performance - Minimal allocation and maximum speed
- 🧩 Dependency Injection - Built-in DI container support
📦 Installation
Install the main package:
dotnet add package eQuantic.Mapper
For auto-generated mappers, also install the generator:
dotnet add package eQuantic.Mapper.Generator
Or via Package Manager Console:
Install-Package eQuantic.Mapper
Install-Package eQuantic.Mapper.Generator
🎯 Quick Start
Basic Usage
// 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; }
}
// Destination model with aggregation
public class UserDestination
{
[MapFrom(typeof(UserSource), new[] { nameof(UserSource.FirstName), nameof(UserSource.LastName) },
MapperPropertyAggregation.ConcatenateWithSpace)]
public string FullName { get; set; } = string.Empty;
[MapFrom(typeof(UserSource), new[] { nameof(UserSource.Salary), nameof(UserSource.Bonus) },
MapperPropertyAggregation.Sum)]
public decimal TotalIncome { get; set; }
public int Age { get; set; } // Auto-mapped by name
}
// Auto-generated mapper
[Mapper(typeof(UserSource), typeof(UserDestination))]
public partial class UserMapper : IMapper
{
}
Dependency Injection Setup
var builder = WebApplication.CreateBuilder(args);
// Register all mappers
builder.Services.AddMappers();
var app = builder.Build();
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();
🔧 Advanced Features
Property Aggregation
The MapFromAttribute
supports multiple aggregation types for combining source properties:
Available Aggregation Types
Aggregation | Description | Example |
---|---|---|
None |
No aggregation (default) | Simple property mapping |
Concatenate |
Join without separator | "JohnDoe" |
ConcatenateWithSpace |
Join with space | "John Doe" |
ConcatenateWithComma |
Join with comma | "John, Doe" |
ConcatenateWithSeparator |
Join with custom separator | "John-Doe" |
Sum |
Numeric sum | 5000 + 1000 = 6000 |
Max |
Maximum value | Max(5000, 1000) = 5000 |
Min |
Minimum value | Min(5000, 1000) = 1000 |
Average |
Average value | (5000 + 1000) / 2 = 3000 |
FirstNonEmpty |
First non-null/empty value | "John" |
LastNonEmpty |
Last non-null/empty value | "Doe" |
Count |
Count of non-null values | 2 |
Complex Aggregation Examples
public class PersonSource
{
public string FirstName { get; set; } = string.Empty;
public string MiddleName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Department { get; set; } = string.Empty;
public string Position { get; set; } = string.Empty;
public decimal Salary { get; set; }
public decimal Bonus { get; set; }
public decimal Commission { get; set; }
public int WorkHours { get; set; }
public int OvertimeHours { get; set; }
}
public class PersonDestination
{
// String concatenation with space
[MapFrom(typeof(PersonSource),
new[] { nameof(PersonSource.FirstName), nameof(PersonSource.LastName) },
MapperPropertyAggregation.ConcatenateWithSpace)]
public string FullName { get; set; } = string.Empty;
// Custom separator concatenation
[MapFrom(typeof(PersonSource),
new[] { nameof(PersonSource.Department), nameof(PersonSource.Position) },
MapperPropertyAggregation.ConcatenateWithSeparator, " - ")]
public string JobTitle { get; set; } = string.Empty;
// Numeric aggregation - Sum
[MapFrom(typeof(PersonSource),
new[] { nameof(PersonSource.Salary), nameof(PersonSource.Bonus), nameof(PersonSource.Commission) },
MapperPropertyAggregation.Sum)]
public decimal TotalIncome { get; set; }
// Numeric aggregation - Average
[MapFrom(typeof(PersonSource),
new[] { nameof(PersonSource.Salary), nameof(PersonSource.Bonus), nameof(PersonSource.Commission) },
MapperPropertyAggregation.Average)]
public decimal AverageIncome { get; set; }
// Get maximum value
[MapFrom(typeof(PersonSource),
new[] { nameof(PersonSource.Salary), nameof(PersonSource.Bonus) },
MapperPropertyAggregation.Max)]
public decimal HighestPayComponent { get; set; }
// Count non-null fields
[MapFrom(typeof(PersonSource),
new[] { nameof(PersonSource.FirstName), nameof(PersonSource.Department), nameof(PersonSource.Position) },
MapperPropertyAggregation.Count)]
public int NonNullFieldsCount { get; set; }
// First non-empty value
[MapFrom(typeof(PersonSource),
new[] { nameof(PersonSource.FirstName), nameof(PersonSource.LastName), nameof(PersonSource.MiddleName) },
MapperPropertyAggregation.FirstNonEmpty)]
public string? PreferredName { get; set; }
}
// Generated mapper
[Mapper(typeof(PersonSource), typeof(PersonDestination))]
public partial class PersonAggregationMapper : IMapper
{
}
Async Mapping Support
[Mapper(typeof(UserSource), typeof(UserDestination))]
public partial class AsyncUserMapper : IAsyncMapper
{
}
// Usage
var mapper = mapperFactory.GetAsyncMapper<UserSource, UserDestination>()!;
var result = await mapper.MapAsync(source, cancellationToken);
Context-Aware Mapping
public class MappingContext
{
public string TenantId { get; set; } = string.Empty;
public bool IncludeSensitiveData { get; set; }
}
[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;
}
};
}
}
Custom Mapping Logic
[Mapper(typeof(UserSource), typeof(UserDestination))]
public partial class CustomUserMapper : IMapper
{
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}";
}
};
}
}
Custom Constructor
[Mapper(typeof(UserSource), typeof(UserDestination), OmitConstructor = true)]
public partial class CustomConstructorMapper : IMapper
{
private readonly ILogger<CustomConstructorMapper> _logger;
public CustomConstructorMapper(IMapperFactory mapperFactory, ILogger<CustomConstructorMapper> logger)
{
MapperFactory = mapperFactory;
_logger = logger;
OnAfterMap += (sender, args) =>
{
_logger.LogInformation("Mapped user: {FullName}", args.Destination.FullName);
};
}
}
🔍 Generated Code Example
For the aggregation example above, the generator produces optimized code like:
public virtual PersonDestination? Map(PersonSource? source, PersonDestination? destination)
{
if (source == null) return null;
if (destination == null) return Map(source);
InvokeHandler(OnBeforeMap, new MapEventArgs<PersonSource, PersonDestination>(source, destination));
destination.FullName = string.Join(" ", new object?[] { source.FirstName, source.LastName }
.Where(x => x != null && !string.IsNullOrEmpty(x.ToString()))
.Select(x => x.ToString()));
destination.JobTitle = string.Join(" - ", new object?[] { source.Department, source.Position }
.Where(x => x != null && !string.IsNullOrEmpty(x.ToString()))
.Select(x => x.ToString()));
destination.TotalIncome = new[] { source.Salary, source.Bonus, source.Commission }.Sum();
destination.AverageIncome = new[] { source.Salary, source.Bonus, source.Commission }.Average();
destination.HighestPayComponent = new[] { source.Salary, source.Bonus }.Max();
destination.NonNullFieldsCount = new object?[] { source.FirstName, source.Department, source.Position }.Count(x => x != null);
destination.PreferredName = new object?[] { source.FirstName, source.LastName, source.MiddleName }
.Where(x => x != null && !string.IsNullOrEmpty(x.ToString()))
.FirstOrDefault()?.ToString();
InvokeHandler(OnAfterMap, new MapEventArgs<PersonSource, PersonDestination>(source, destination));
return destination;
}
📋 Backward Compatibility
The library maintains full backward compatibility. Existing single-property mappings continue to work:
public class LegacyDestination
{
[MapFrom(typeof(UserSource), nameof(UserSource.FirstName))] // Still works!
public string Name { get; set; } = string.Empty;
}
🛠️ Development & Debugging
For debugging the source generator during development:
#if DEBUG
SpinWait.SpinUntil(() => Debugger.IsAttached);
#endif
📊 Performance
eQuantic.Mapper generates highly optimized code with:
- Zero reflection - All mapping logic is compile-time generated
- Minimal allocations - Efficient object creation and property assignment
- Type safety - Full compile-time type checking
- Inlining-friendly - Code structure optimized for JIT inlining
🤝 Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
📄 License
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
🔗 Links
⭐ If you find this library useful, please give it a star! ⭐
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. 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 is compatible. 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 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. |
.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 was computed. |
.NET Framework | net461 was computed. net462 was computed. 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. |
-
.NETStandard 2.0
-
net6.0
-
net7.0
-
net8.0
NuGet packages (1)
Showing the top 1 NuGet packages that depend on eQuantic.Mapper:
Package | Downloads |
---|---|
eQuantic.Core.Application.Crud
eQuantic Application CRUD Library |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last Updated |
---|---|---|
1.9.0 | 142 | 7/8/2025 |
1.8.0 | 130 | 7/6/2025 |
1.7.0 | 425 | 5/29/2025 |
1.6.3 | 168 | 5/27/2025 |
1.6.2 | 148 | 5/26/2025 |
1.6.1 | 147 | 5/10/2025 |
1.6.0 | 381 | 2/23/2025 |
1.5.1 | 338 | 1/20/2025 |
1.5.0 | 119 | 1/20/2025 |
1.4.0 | 1,565 | 9/18/2024 |
1.3.6 | 917 | 7/23/2024 |
1.3.5 | 163 | 7/22/2024 |
1.3.4 | 137 | 7/22/2024 |
1.3.3 | 141 | 7/22/2024 |
1.3.2 | 247 | 7/20/2024 |
1.3.1 | 140 | 7/15/2024 |
1.3.0 | 183 | 7/1/2024 |
1.2.8 | 154 | 6/28/2024 |
1.2.7 | 136 | 6/28/2024 |
1.2.6 | 136 | 6/28/2024 |
1.2.5 | 143 | 6/28/2024 |
1.2.4 | 785 | 5/5/2024 |
1.2.3 | 150 | 5/4/2024 |
1.2.2 | 294 | 5/4/2024 |
1.2.1 | 216 | 5/3/2024 |
1.2.0 | 124 | 5/3/2024 |
1.1.9 | 213 | 4/23/2024 |
1.1.8 | 136 | 4/23/2024 |
1.1.7 | 131 | 4/23/2024 |
1.1.6 | 135 | 4/23/2024 |
1.1.5 | 2,139 | 11/18/2023 |
1.1.4 | 1,503 | 8/2/2023 |
1.1.3 | 609 | 7/15/2023 |
1.1.2 | 218 | 7/15/2023 |
1.1.1 | 205 | 7/15/2023 |
1.1.0 | 347 | 5/18/2023 |
1.0.2 | 366 | 1/10/2023 |
1.0.1 | 357 | 1/9/2023 |
1.0.0 | 358 | 1/8/2023 |
DTOs mapping without reflection