Chd.Mapping.Roslyn.Advanced
8.1.6
dotnet add package Chd.Mapping.Roslyn.Advanced --version 8.1.6
NuGet\Install-Package Chd.Mapping.Roslyn.Advanced -Version 8.1.6
<PackageReference Include="Chd.Mapping.Roslyn.Advanced" Version="8.1.6" />
<PackageVersion Include="Chd.Mapping.Roslyn.Advanced" Version="8.1.6" />
<PackageReference Include="Chd.Mapping.Roslyn.Advanced" />
paket add Chd.Mapping.Roslyn.Advanced --version 8.1.6
#r "nuget: Chd.Mapping.Roslyn.Advanced, 8.1.6"
#:package Chd.Mapping.Roslyn.Advanced@8.1.6
#addin nuget:?package=Chd.Mapping.Roslyn.Advanced&version=8.1.6
#tool nuget:?package=Chd.Mapping.Roslyn.Advanced&version=8.1.6
Chd.Mapping.Roslyn.Advanced β High-Performance, Expression-Aware DTO Mapper
Chd (Cleverly Handle Difficulty) library helps you cleverly handle difficulty, write code quickly, and keep your application stable.
Chd.Mapping.Roslyn.Advanced is a blazing-fast, compile-time object mapper for .NET with expression-based mapping supportβbuilt on Roslyn source generators for maximum performance and type safety!
π Table of Contents
- About
- Why This Package?
- Features
- Installation
- Quick Start
- Usage Examples
- IDE Experience
- Performance
- Comparison with Other Mappers
- Best Practices
- Limitations
- Troubleshooting
- Viewing Generated Code
- FAQ
- Examples Repository
- Contributing
- Authors
- Acknowledgments
π§ About
Chd.Mapping.Roslyn.Advanced is a modern, compile-time object mapper for .NET, built with Roslyn source generators. It automatically creates fast, type-safe, and debuggable mapping code between DTOs and entities during your build processβeliminating runtime reflection, configuration headaches, and mapping errors typical of tools like AutoMapper or Mapster.
This advanced version supports not only property-to-property mapping, but also expression-based mapping for calculated or derived properties, nested object mapping, and high-performance scenarios.
π Why this package
- Zero Reflection, Maximum Speed: All mapping logic is generated at build time. No runtime cost, no dynamic code, no hidden performance penalty.
- Type Safety: All mapping is verified at buildβif your DTO or entity changes, mapping errors become compiler errors, not runtime bugs.
- Debuggability: All generated mapping code is standard C#, fully visible in your IDE. Set breakpoints, watch variables, and step through mappings with the debugger.
- Simplicity: No configuration files, no dependency injection, no startup scanning, no magic. Just add attributes, build, and use.
- Advanced Scenarios: Supports custom expressions, nested objects, collections, and property name mismatches with a single attribute.
β¨ Features
- Attribute-based mapping:
[MapTo],[MapProperty](with expression support) - Compile-time generation of implicit mapping operators
- Expression-based property mapping (e.g.,
[MapProperty("Price + Tax - Discount")]) - No runtime logic. Just generated code: (e.g.,
NetTotal = entity.Price + entity.Tax - entity.Discount;) - Nested and collection mapping support
- No runtime dependency, no reflection, no configuration files
- Full support for .NET 8+, .NET 9, and .NET Standard 2.0 SDK-style projects
- Live IntelliSense inside
[MapProperty("β¦")]β counterpart property names are suggested while you type - Roslyn analyzers for instant feedback in the editor:
MAP001β class decorated with[MapTo]is missing thepartialmodifier (ships with a one-click code fix)MAP002β unknown identifier inside[MapProperty("β¦")], reported on the attribute itself
- Defensive generator β when an expression contains an unknown identifier, that single line is omitted from the generated operator instead of breaking the build inside
*.g.cs - Global-namespace classes are fully supported (no
namespace <global namespace>block is emitted)
π¦ Installation
Install via .NET CLI:
dotnet add package Chd.Mapping.Roslyn.Advanced
Or via NuGet Package Manager Console:
Install-Package Chd.Mapping.Roslyn.Advanced
Or via Package Manager UI in Visual Studio / Rider.
Requirements:
- .NET Standard 2.0+
- .NET Core 3.1+
- .NET 5, 6, 7, 8, 9+
- C# 8.0 or higher
π Quick Start
using Chd.Mapping.Abstractions;
namespace ChdRoslynMappingTest
{
// 1. Mark your DTO with [MapTo] and expression mapping
[MapTo(typeof(OrderEntity))]
public partial class OrderDto
{
public decimal Price { get; set; }
public decimal Tax { get; set; }
public decimal Discount { get; set; }
// Expression-based calculated property!
[MapProperty("Price * (Tax + 100) / 100 - Discount")]
public decimal NetTotal { get; set; }
}
// 2. Define your Entity (must be partial)
public partial class OrderEntity
{
public decimal Price { get; set; }
public decimal Tax { get; set; }
public decimal Discount { get; set; }
public decimal NetTotal { get; set; }
}
// 3. Use implicit operators - that's it!
internal class Program
{
static void Main(string[] args)
{
var dto = new OrderDto { Price = 100, Tax = 18, Discount = 2 };
OrderEntity entity = dto; // DTO β Entity with calculation!
Console.WriteLine($"NetTotal: {entity.NetTotal}"); // Output: 116
}
}
}
Output:
NetTotal: 116
β Expression-based mapping! β No reflection overhead! β Full IntelliSense support! β Compile-time validation!
π‘ Usage Examples
1. Basic Mapping
Scenario: Simple DTO β Entity mapping with identical property names.
using Chd.Mapping.Abstractions;
using System;
[MapTo(typeof(UserEntity))]
public partial class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
public partial class UserEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
class Program
{
static void Main()
{
// DTO to Entity
var dto = new UserDto
{
Id = 1,
Name = "Mehmet",
Email = "mehmet@example.com"
};
UserEntity entity = dto; // Implicit mapping
Console.WriteLine($"Entity: {entity.Id}, {entity.Name}, {entity.Email}");
// Entity back to DTO
UserDto dto2 = entity;
Console.WriteLine($"DTO: {dto2.Id}, {dto2.Name}, {dto2.Email}");
}
}
Console Output:
Entity: 1, Mehmet, mehmet@example.com
DTO: 1, Mehmet, mehmet@example.com
2. Advanced Property Mapping
Scenario: Map properties with different names.
using Chd.Mapping.Abstractions;
using System;
[MapTo(typeof(UserEntity))]
public partial class UserDto
{
public int Id { get; set; }
[MapProperty("FullName")] // Maps DTO.Name β Entity.FullName
public string Name { get; set; }
[MapProperty("EmailAddress")] // Maps DTO.Email β Entity.EmailAddress
public string Email { get; set; }
}
public partial class UserEntity
{
public int Id { get; set; }
public string FullName { get; set; }
public string EmailAddress { get; set; }
}
class Program
{
static void Main()
{
var dto = new UserDto
{
Id = 42,
Name = "Mehmet YoldaΕ",
Email = "mehmet@example.com"
};
UserEntity entity = dto;
Console.WriteLine($"Id: {entity.Id}");
Console.WriteLine($"FullName: {entity.FullName}");
Console.WriteLine($"EmailAddress: {entity.EmailAddress}");
}
}
Console Output:
Id: 42
FullName: Mehmet YoldaΕ
EmailAddress: mehmet@example.com
Expression-Based Mapping
π₯ The Power of Advanced Mapping: Calculate derived properties with C# expressions!
using Chd.Mapping.Abstractions;
using System;
[MapTo(typeof(OrderEntity))]
public partial class OrderDto
{
public decimal Price { get; set; }
public decimal Tax { get; set; }
public decimal Discount { get; set; }
// Mathematical expression
[MapProperty("Price * (Tax + 100) / 100 - Discount")]
public decimal NetTotal { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
// String concatenation with single quotes
[MapProperty("Name + ' ' + Surname")]
public string FullName { get; set; }
public bool IsActive { get; set; }
// Ternary operator
[MapProperty("IsActive ? 'Active' : 'Passive'")]
public string StatusText { get; set; }
}
public partial class OrderEntity
{
public decimal Price { get; set; }
public decimal Tax { get; set; }
public decimal Discount { get; set; }
public decimal NetTotal { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string FullName { get; set; }
public bool IsActive { get; set; }
public string StatusText { get; set; }
}
class Program
{
static void Main()
{
var dto = new OrderDto
{
Price = 100,
Tax = 18,
Discount = 2,
Name = "Mehmet",
Surname = "YoldaΕ",
IsActive = true
};
OrderEntity entity = dto; // Expression-based mapping!
Console.WriteLine($"NetTotal: {entity.NetTotal}"); // 116
Console.WriteLine($"FullName: {entity.FullName}"); // Mehmet YoldaΕ
Console.WriteLine($"StatusText: {entity.StatusText}"); // Active
// Reverse mapping works too!
OrderDto dto2 = entity;
Console.WriteLine($"DTO NetTotal: {dto2.NetTotal}"); // 116
}
}
Console Output:
NetTotal: 116
FullName: Mehmet YoldaΕ
StatusText: Active
DTO NetTotal: 116
π‘ Pro Tip:
You can use either single quotes (' ') or double quotes (" ") for string literals in expressions.
Recommended style: Single quotes ([MapProperty("Name + ' ' + Surname")]) for better readability.
Supported Expression Types:
- β
Mathematical operations:
+,-,*,/,% - β
Comparison:
==,!=,<,>,<=,>= - β
Logical:
&&,||,! - β
Ternary operator:
condition ? true : false - β
String concatenation:
+ - β
Property access:
Property.SubProperty - β
Method calls:
Property.ToString() - β
Parentheses for grouping:
(A + B) * C
Generated Code Example:
The generator creates highly optimized code like this:
// <auto-generated />
public partial class OrderEntity
{
public static implicit operator OrderDto(OrderEntity entity)
{
if(entity == null) return null;
return new OrderDto
{
Price = entity.Price,
Tax = entity.Tax,
Discount = entity.Discount,
NetTotal = entity.Price * (entity.Tax + 100) / 100 - entity.Discount,
Name = entity.Name,
Surname = entity.Surname,
FullName = entity.Name + " " + entity.Surname,
IsActive = entity.IsActive,
StatusText = entity.IsActive ? "Active" : "Passive"
};
}
}
β
Zero reflection - just pure, optimized C# code!
β
Fully debuggable - set breakpoints and step through!
β
Type-safe - compile-time validation!
3. Nested and Collection Mapping
Scenario: Automatically map nested objects and collections.
using Chd.Mapping.Abstractions;
using System;
using System.Collections.Generic;
[MapTo(typeof(ProductEntity))]
public partial class ProductDto
{
public string Code { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
public partial class ProductEntity
{
public string Code { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
}
[MapTo(typeof(OrderEntity))]
public partial class OrderDto
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
public List<ProductDto> Products { get; set; }
}
public partial class OrderEntity
{
public int OrderId { get; set; }
public string CustomerName { get; set; }
public List<ProductEntity> Products { get; set; }
}
class Program
{
static void Main()
{
var dto = new OrderDto
{
OrderId = 3,
CustomerName = "Acme Corp",
Products = new List<ProductDto>
{
new ProductDto { Code = "A123", Quantity = 2, Price = 49.99m },
new ProductDto { Code = "B456", Quantity = 5, Price = 29.99m }
}
};
// Automatic nested collection mapping!
OrderEntity entity = dto;
Console.WriteLine($"Order #{entity.OrderId} - {entity.CustomerName}");
Console.WriteLine("Products:");
foreach (var p in entity.Products)
Console.WriteLine($" {p.Code}: {p.Quantity}x @ ${p.Price}");
}
}
Console Output:
Order #3 - Acme Corp
Products:
A123: 2x @ $49.99
B456: 5x @ $29.99
β
Nested objects mapped automatically!
β
Collections supported: List<T>, IEnumerable<T>, arrays, etc.
β
Deep nesting works recursively!
4. Debugging and Maintainability
All mapping code is standard C# and fully debuggable:
View Generated Files:
- Visual Studio: Solution Explorer β Dependencies β Analyzers β Chd.Mapping.Roslyn.Advanced
- Or check
obj/Debug/netX.X/generated/folder
Set Breakpoints:
- Navigate to generated
implicit operatormethods - Step through mapping code line by line
- Inspect variable values
- Navigate to generated
IntelliSense Support:
- Full code completion
- Navigate to definition (F12)
- Find all references
- Refactoring support
Example Generated Code:
public static implicit operator UserEntity(UserDto source)
{
if (source == null) return null;
return new UserEntity
{
Id = source.Id,
FullName = source.Name,
EmailAddress = source.Email
};
}
Generated Code is:
- β Readable and maintainable
- β Optimized by the C# compiler
- β No runtime overhead
- β Type-safe at compile time
Tip β making generated files visible on disk: add the following to the consuming project so the generated
.g.csfiles are written next to your source and are clickable from Error List:<PropertyGroup> <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> <CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath> </PropertyGroup> <ItemGroup> <Compile Remove="Generated\**" /> </ItemGroup>After the next build, generated mapping code lives under
Generated/Chd.Mapping.Roslyn.Advanced/MappingGenerator/β¦andβ¦/CollectionMappingGenerator/β¦.
π IDE Experience
In addition to the source generator, the package ships with a Roslyn CompletionProvider, two DiagnosticAnalyzers and one CodeFixProvider, so most mapping mistakes become editor errors before you even build.
IntelliSense inside [MapProperty("β¦")]
Place the caret between the quotes of [MapProperty("|")], press Ctrl+Space, and the IDE lists the public instance properties of the counterpart class (the one referenced by [MapTo(typeof(...))]). The same names are valid inside expressions such as Name + ' ' + Surname or IsActive ? 'Active' : 'Passive', because the generator rewrites those identifiers as entity.Name, entity.Surname, entity.IsActive etc., where entity is of the counterpart type.
Analyzer Diagnostics
| ID | Severity | What it catches |
|---|---|---|
MAP001 |
Error | A class decorated with [MapTo] is missing the partial modifier. Ships with a code fix. |
MAP002 |
Error | An identifier used inside a [MapProperty("β¦")] expression is not a public property of the counterpart type. The squiggle points at the exact word inside the attribute string. |
MAP002 example:
[MapTo(typeof(OrderEntity))]
public partial class OrderDto
{
// OrderEntity has no "Surnam" property.
[MapProperty("Name + ' ' + Surnam")] // β red squiggle on Surnam
public string FullName { get; set; }
}
MAP002: 'Surnam' is not a public property of 'OrderEntity'. The expression in [MapProperty] must reference properties of the counterpart type.
Double-clicking the entry in Error List navigates straight to the typo β not to a *.g.cs file. Identifiers inside single-quoted segments (e.g. the Active / Passive in "IsActive ? 'Active' : 'Passive'") and C# reserved tokens (true, false, null, β¦) are not flagged.
When MAP002 triggers, the generator also defensively skips that single map line, so the build does not break inside generated files; you only see one clear error on the attribute. Fix the typo and the line reappears in the next build.
Code Fix: Add partial Modifier
MAP001 is paired with MapToMustBePartialCodeFixProvider. Place the caret on the squiggled class name, press Ctrl+. (Quick Actions) and choose Add 'partial' modifier β Roslyn rewrites the declaration in place:
// Before
[MapTo(typeof(Entity))]
public class MyDto { ... }
// After (one click)
[MapTo(typeof(Entity))]
public partial class MyDto { ... }
π¦ Performance
| Scenario | AutoMapper (ms) | Mapster (ms) | Chd.Mapping.Roslyn.Advanced (ms) |
|---|---|---|---|
| 1,000,000 DTOβEntity mappings | 980 | 410 | 180 |
| 100,000 EntityβDTO mappings w/nesting | 180 | 74 | 34 |
| Flat object, assign all properties | 22 | 10 | 4 |
Source: Internal benchmarks, see /benchmarks.
Key Takeaway:
Chd.Mapping.Roslyn.Advanced mapping is as fast as hand-written code, and dramatically faster than reflection-based mappers.
β Comparison with Other Mappers
| Capability | AutoMapper | Mapster | Chd.Mapping.Roslyn.Advanced |
|---|---|---|---|
| Mapping time | Runtime (reflection / IL emit) | Runtime + optional codegen | Compile time only |
| Configuration / profiles | Required | Optional | None β attributes only |
| Startup scanning | Yes | Yes (when reflection mode) | No |
| Debuggable mapping code | β (IL or expression trees) | Partial | β Plain C# you can step through |
| Expression-based derived properties | Via .ForMember(... => Expression) |
Via .Map(...) config |
β
[MapProperty("Price + Tax")] |
| Nested objects | β | β | β |
| Collections | β | β | β |
| Compile-time validation | β (runtime) | Partial (codegen mode) | β C# compiler + MAP001 / MAP002 |
| IntelliSense in mapping config | β | β | β
CompletionProvider inside [MapProperty] |
| Analyzer for typos | β | β | β
MAP002 on the attribute |
Quick fix for missing partial |
n/a | n/a | β
MAP001 + Code Fix |
| Reflection at runtime | Yes | Yes (default) | Never |
| Cold start cost | High | Medium | Zero |
| Trim / AOT friendly | β Partial | β Partial | β Yes β no reflection |
| External runtime DLL | AutoMapper.dll |
Mapster.dll |
None β only Chd.Mapping.Abstractions (attributes) |
The differentiator of Chd.Mapping.Roslyn.Advanced is not raw speed alone (any compile-time mapper is fast); it is the end-to-end IDE experience β completion, analyzers and code fixes all ship in the same package.
π Best Practices and Tips
- Mark all mapped classes as
partial. - Use
[MapTo(typeof(TargetType))]on your DTOs. - Use
[MapProperty("...")]for calculated or custom-mapped properties. - Run a build to regenerate mapping code after model changes.
- No Fody, no config files, no runtime setup required.
β Limitations
[MapProperty]attribute only supports a single string parameter: use either the target property name or a full C# expression.- Expressions must be valid C# and use property names exactly as declared.
- Nested mapping works if types on both sides also have
[MapTo]or compatible structure. - Custom type conversions outside of C# syntax, external services, complex global mapping config are not supported.
- Circular object graph mapping is not supported.
- Classes marked with
[MapTo]must be declared aspartial.- If you forget to add
partial, youβll see a build-time diagnostic error (MAP001):
"Class 'X' is marked with [MapTo] and must be declared as partial" - Visual Studio, Rider, and similar IDEs provide a quick fix (lightbulb action) for this β βAdd 'partial' modifierβ.
- With a single click, the
partialkeyword is automatically added to your class declaration:// Incorrect: [MapTo(typeof(Entity))] public class MyDto { ... } // Fixed: [MapTo(typeof(Entity))] public partial class MyDto { ... }
- If you forget to add
π Troubleshooting
- MAP001: Class must be partial
- Solution: Add
partialto your class declaration (e.g.,public partial class ...). - Your IDE (VS, Rider) typically offers a quick fix ("Add 'partial' modifier"/lightbulb) for this β just click to apply the fix!
- Solution: Add
- MAP002: '<name>' is not a public property of '<Counterpart>'
- Solution: fix the spelling, add the missing property to the counterpart type, or remove the
[MapProperty]if it is no longer needed. Identifiers inside single-quoted segments (e.g.'Active') and reserved tokens (true,false,null) are intentionally not flagged.
- Solution: fix the spelling, add the missing property to the counterpart type, or remove the
- Build error inside
*.g.cs: Should not happen anymore βMAP002is emitted instead of broken generated code, and the offending line is omitted from the operator. If you still see one, please file an issue with the failing snippet. - IntelliSense suggestions do not appear inside
[MapProperty("")]: Make sure your IDE uses Roslyn β₯ 4.8 (Visual Studio 2022 17.8+, Rider 2024.1+), and reload the consuming project (Solution Explorer β right-click β Unload / Reload) so the IDE picks up the analyzer DLL. - Squiggle remains after the issue is fixed: Roslyn caches diagnostics aggressively. Touch the file (add and remove a space) or Build β Clean + Rebuild. As a last resort, close the solution and clear the IDE component-model cache.
- Build error after changing models: Run a clean build (
dotnet clean && dotnet build). Check generated sources in/obj(or in yourGenerated/folder ifEmitCompilerGeneratedFilesis enabled). - Attribute typo or syntax error: Ensure
[MapTo]and[MapProperty]are spelled correctly, and property names are valid. - Expression compile errors: Expressions must be valid C#. If an error occurs, check the generated code and fix the expression.
π Viewing the Generated Mapping Code
- Open
/obj/Debug/net*/*Mapping*.g.cs(or your IDEβs generated files node) to inspect all mapping operator code. - You can debug into the generated code just like any hand-written C#.
β FAQ
Q: Do I need to write any mapping code?
A: No. Just add attributes and build β all mapping code is generated for you.
Q: Can I debug the generated code?
A: Yes! All generated code is standard C# and fully debuggable.
Q: Is there any runtime dependency?
A: No. All mapping logic is generated at compile time.
Q: What if I change my DTO or Entity?
A: The generator will update mappings on the next build. Mapping errors become compiler errors.
π€ Contributing
Contributions, issues and feature requests are welcome!
Check issues page or submit a pull request.
π§ͺ Examples Repository
This repository contains examples of how to use the Library.Mapping package in different scenarios, including DTO-Entity mapping, operator injection, and performance benchmarks.
π€ Authors
π Acknowledgments
- Thanks to all contributors and users of the CHD library ecosystem.
- Inspired by best practices in .NET library design.
For issues, feature requests, or contributions, please visit the GitHub repository
Learn more about Target Frameworks and .NET Standard.
-
.NETStandard 2.0
- Chd.Mapping.Abstractions (>= 8.0.5)
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 |
|---|---|---|
| 8.1.6 | 92 | 6/17/2026 |
| 8.1.5 | 102 | 6/16/2026 |
| 8.1.4 | 95 | 6/13/2026 |
| 8.1.3 | 132 | 2/28/2026 |
| 8.1.2 | 116 | 2/19/2026 |
| 8.1.1 | 293 | 1/28/2026 |
| 8.0.9 | 124 | 1/23/2026 |
| 8.0.7 | 129 | 1/21/2026 |
| 8.0.6 | 125 | 1/20/2026 |
| 8.0.5 | 119 | 1/20/2026 |
| 8.0.4 | 119 | 1/20/2026 |
| 8.0.3 | 122 | 1/20/2026 |
| 8.0.2 | 124 | 1/20/2026 |
| 8.0.1 | 121 | 1/19/2026 |