Chd.Mapping.Roslyn.Advanced
8.1.3
dotnet add package Chd.Mapping.Roslyn.Advanced --version 8.1.3
NuGet\Install-Package Chd.Mapping.Roslyn.Advanced -Version 8.1.3
<PackageReference Include="Chd.Mapping.Roslyn.Advanced" Version="8.1.3" />
<PackageVersion Include="Chd.Mapping.Roslyn.Advanced" Version="8.1.3" />
<PackageReference Include="Chd.Mapping.Roslyn.Advanced" />
paket add Chd.Mapping.Roslyn.Advanced --version 8.1.3
#r "nuget: Chd.Mapping.Roslyn.Advanced, 8.1.3"
#:package Chd.Mapping.Roslyn.Advanced@8.1.3
#addin nuget:?package=Chd.Mapping.Roslyn.Advanced&version=8.1.3
#tool nuget:?package=Chd.Mapping.Roslyn.Advanced&version=8.1.3
📘 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
- Performance
- 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 = Price + Tax - 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
📦 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;
// 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!
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
public string StatusText { get; set; }
public bool IsActive { get; set; }
}
class Program { static void Main() { OrderDto dto = new OrderDto() { Price = 100, Tax = 18, Discount = 2, Name = "Mehmet", Surname = "Yoldaş", IsActive = true }; OrderEntity entity = dto; dto = entity; Console.WriteLine($"NetTotal: {dto.NetTotal}"); // 116 Console.WriteLine($"FullName: {dto.FullName}"); // Mehmet Yoldaş Console.WriteLine($"StatusText: {dto.StatusText}"); // Active } }
**Console Output:**
NetTotal: 116 FullName: Mehmet Yoldaş StatusText: Active
> **Tip:**
> You can use either single quotes (`' '`) or double quotes (`" "`) for string concatenation in expressions.
> For readability, the recommended style is single quotes (`' '`), as in `[MapProperty("Name + ' ' + Surname")]`.
---
**Generated Code Example:**
When building the above example, the generator creates code similar to this:
```csharp
// <auto-generated />
namespace Mapping.Test
{
public partial class OrderEntity
{
public static implicit operator global::Mapping.Test.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"
};
}
}
}
Therefore, you can debug and step through the generated mapping logic as needed. Runtime performance is equivalent to hand-written code. There is no reflection or dynamic code involved.None of the mapping logic is executed at runtime; everything is pre-compiled. Runtime errors due to mapping issues are eliminated.
3. Nested and Collection Mapping
Scenario: Map nested objects and collections automatically.
using Chd.Mapping.Abstractions;
using System;
using System.Collections.Generic;
[MapTo(typeof(ProductEntity))]
public class ProductDto
{
public string Code { get; set; }
public int Quantity { get; set; }
}
public partial class ProductEntity
{
public string Code { get; set; }
public int Quantity { get; set; }
}
[MapTo(typeof(OrderEntity))]
public class OrderDto
{
public int OrderId { get; set; }
public List<ProductDto> Products { get; set; }
}
public partial class OrderEntity
{
public int OrderId { get; set; }
public List<ProductEntity> Products { get; set; }
}
class Program
{
static void Main()
{
var dto = new OrderDto
{
OrderId = 3,
Products = new List<ProductDto>
{
new ProductDto { Code = "A123", Quantity = 2 },
new ProductDto { Code = "B456", Quantity = 5 }
}
};
OrderEntity entity = dto;
Console.WriteLine($"OrderId: {entity.OrderId}");
foreach (var p in entity.Products)
Console.WriteLine($"Product: {p.Code} - {p.Quantity}");
}
}
Console Output:
OrderId: 3
Product: A123 - 2
Product: B456 - 5
4. Debugging and Maintainability
- All generated mapping code is standard C#, located in
/objor/Generatedfolders. - You can set breakpoints, watch variables, and step through mappings with the debugger.
- Example generated code:
public static implicit operator UserEntity(UserDto dto) { if (dto == null) return null; return new UserEntity { Id = dto.Id, Name = dto.Name, // ... other properties }; }
🚦 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.
🏆 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
- Build error after changing models: Run a clean build (
dotnet clean && dotnet build). Check generated sources in/obj. - 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.
Test Repository For Usage
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.