=============== LIBRARY RULES =============== From library maintainers: - Mapper: every destination property must be explicitly mapped, optional, or ignored; unmapped properties fail at build time. No convention or name-based auto-mapping and no attributes. Define mappings in a MapperContext, modeled on EF Core's DbContext. - Mapper: mapping expressions must be EF Core-translatable (no throw, no delegate invocation), so one definition drives both in-memory mapping and IQueryable/EF Core projection. - Localization: strings are authored once at the call site as an ICU MessageFormat default (source of truth and fallback) and extracted at compile time. Keys are scoped by category, following the ILogger model. - Libraries depend only on the BCL, with opt-in DI and EF Core integration packages. No runtime reflection on the hot path; AOT/trim-safe where documented. ### Install EF Core Companion Package Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/getting-started.md Install the optional companion package for enhanced EF Core integration, enabling direct mapper calls within queries. ```bash dotnet add package ArchPillar.Extensions.Mapper.EntityFrameworkCore ``` -------------------------------- ### In-Memory Collection Projection Example Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Shows how to project an in-memory IEnumerable collection using a mapper. ```csharp // In-memory collection projection var dtos = orders.Project(mapper.Order).ToList(); ``` -------------------------------- ### EF Core Query Projection Example Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Demonstrates projecting an IQueryable from EF Core, including filtering and setting custom values. ```csharp // EF Core query projection var results = await dbContext.Orders .Where(o => o.IsActive) .Project(mapper.Order, o => o .Include(m => m.CustomerName) .Set(mapper.CurrentUserId, currentUser.Id)) .ToListAsync(); ``` -------------------------------- ### Coverage Validation Example Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/getting-started.md This example demonstrates how coverage validation works by intentionally omitting a mapping for 'UnitPrice'. It will throw an InvalidOperationException at build time. ```csharp // This throws — UnitPrice is not mapped OrderLine = CreateMapper() .Map(d => d.ProductName, s => s.ProductName) .Map(d => d.Quantity, s => s.Quantity); // InvalidOperationException: "The following properties of OrderLineDto are not mapped: UnitPrice" ``` -------------------------------- ### Install ArchPillar Extensions Mapper Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/getting-started.md Add the core ArchPillar Extensions Mapper NuGet package to your .NET project. ```bash dotnet add package ArchPillar.Extensions.Mapper ``` -------------------------------- ### EF Core Projection Example Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/getting-started.md Use the mapper to project data directly from an EF Core DbContext, leveraging LINQ provider optimization. ```csharp var results = await dbContext.Orders .Where(o => o.IsActive) .Project(mappers.Order) .ToListAsync(); ``` -------------------------------- ### In-Memory Mapping Example Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/getting-started.md Perform in-memory mapping of an object using the defined mapper instance. ```csharp var mappers = new AppMappers(); OrderDto dto = mappers.Order.Map(order); ``` -------------------------------- ### Implementing a Custom CastTransformer Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Demonstrates creating a custom transformer by extending `CastTransformer`. This example, `MoneyToAmountTransformer`, replaces a cast from `Money` to `decimal` with an expression that accesses the `Amount` property. The source type matching uses `IsAssignableTo`, allowing base classes or interfaces to match derived or implementing types. ```csharp public sealed class MoneyToAmountTransformer : CastTransformer { protected override Expression Replacement(Expression operand) => Expression.Property(operand, nameof(Money.Amount)); } ``` -------------------------------- ### Register MapperContexts in DI Container Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Example of registering MapperContext instances as singletons within an ASP.NET Core dependency injection container. ```csharp services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); ``` -------------------------------- ### EF Core Integration Setup Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Integrate ArchPillar Mapper with Entity Framework Core by calling UseArchPillarMapper on the DbContextOptionsBuilder. This enables query-time support for mappers. ```csharp options.UseArchPillarMapper(); ``` -------------------------------- ### Implementing a Custom MethodCallTransformer Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Shows how to create a custom transformer by extending `MethodCallTransformer`. This example replaces calls to `Money.IsPositive` with an expression that checks if the `Amount` property is greater than zero. `MethodCallTransformer` handles generic methods, inherited methods, and interface methods automatically. ```csharp public sealed class IsPositiveTransformer : MethodCallTransformer { protected override MethodInfo Method { get; } = typeof(Money).GetMethod(nameof(Money.IsPositive))!; protected override Expression Replacement( Expression? instance, IReadOnlyList arguments) => Expression.GreaterThan( Expression.Property(instance!, nameof(Money.Amount)), Expression.Constant(0m)); } ``` -------------------------------- ### Define Application Mappers Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/README.md Define your application's mappers by inheriting from MapperContext and creating named Mapper properties. This example shows how to map Order and OrderLine objects, including nested projections and optional properties. ```csharp public class AppMappers : MapperContext { public Variable CurrentUserId { get; } public Mapper Order { get; } public Mapper OrderLine { get; } public AppMappers() { OrderLine = CreateMapper(src => new OrderLineDto { ProductName = src.Product.Name, Quantity = src.Quantity, UnitPrice = src.UnitPrice, }); Order = CreateMapper(src => new OrderDto { Id = src.Id, Status = src.Status.ToString(), IsOwner = src.OwnerId == CurrentUserId, Lines = src.Lines.Project(OrderLine).ToList(), }) .Optional(dest => dest.CustomerName, src => src.Customer.Name); EagerBuildAll(); } } ``` -------------------------------- ### Accessing Symmetric Enum Mapper Expressions Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Provides examples of accessing the underlying LINQ expressions for forward, nullable forward, reverse, and nullable reverse mappings of a SymmetricEnumMapper. ```csharp Expression> forward = mapper.ToExpression(); Expression> fwdNull = mapper.ToNullableExpression(); Expression> reverse = mapper.ToReverseExpression(); Expression> revNull = mapper.ToReverseNullableExpression(); ``` -------------------------------- ### BenchmarkDotNet Configuration and Environment Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/README.md Details about the BenchmarkDotNet version, operating system, CPU, and .NET SDK used for the benchmarks. This provides context for the performance results. ```text BenchmarkDotNet v0.15.8, Windows 11 (10.0.26200.7840/25H2/2025Update/HudsonValley2) AMD Ryzen 9 5900X 3.70GHz, 1 CPU, 24 logical and 12 physical cores .NET SDK 10.0.200 [Host] : .NET 9.0.14 (9.0.14, 9.0.1426.11910), X64 RyuJIT x86-64-v3 ShortRun : .NET 9.0.14 (9.0.14, 9.0.1426.11910), X64 RyuJIT x86-64-v3 Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 ``` -------------------------------- ### Prefer Member-Init for Simple Mappings Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/recommendations.md Use the member-initializer style for straightforward mappings for conciseness. Use the fluent style for properties requiring .Optional() or .Ignore(), or for complex computed expressions. ```csharp // Preferred for straightforward mappings Order = CreateMapper(src => new OrderDto { Id = src.Id, PlacedAt = src.CreatedAt, Status = src.Status.ToString(), }); // Use fluent style when you need Optional/Ignore or when the member-init // would be awkward (e.g., complex computed expressions) ``` -------------------------------- ### Registering Transformers Globally, Per-Context, and Per-Mapper Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Demonstrates how to register transformers at different scopes. Global transformers apply to all contexts, per-context transformers apply to all mappers within a context, and per-mapper transformers apply to a single mapper. Registration can be done directly or via the `IOptions` pattern. ```csharp // Global — applies to all contexts (register via DI) var globalOptions = new GlobalMapperOptions(); globalOptions.AddTransformer(new CastTransformer()); services.AddSingleton(globalOptions); // Per-context — applies to all mappers in this context public class AppMappers : MapperContext { public AppMappers(GlobalMapperOptions globalOptions) : base(globalOptions) { AddTransformer(new MyContextTransformer()); } } // Per-mapper — applies to a single mapper Order = CreateMapper(...) .WithTransformers(new MyMapperTransformer()); ``` ```csharp services.Configure(o => o.AddTransformer(new CastTransformer())); services.AddSingleton(); public class AppMappers : MapperContext { public AppMappers(IOptions options) : base(options.Value) { AddTransformer(new MyContextTransformer()); } } ``` -------------------------------- ### Eagerly Compile All Mappers Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/getting-started.md Call EagerBuildAll() in the mapper context constructor to compile all mappers at startup. This surfaces mapping errors immediately and eliminates cold-start latency. ```csharp public class AppMappers : MapperContext { public Mapper OrderLine { get; } public Mapper Order { get; } public AppMappers() { OrderLine = CreateMapper(src => new OrderLineDto { ProductName = src.ProductName, Quantity = src.Quantity, UnitPrice = src.UnitPrice, }); Order = CreateMapper(src => new OrderDto { Id = src.Id, PlacedAt = src.CreatedAt, Status = src.Status, Lines = src.Lines.Project(OrderLine).ToList(), }); EagerBuildAll(); // Compile all mappers now } } ``` -------------------------------- ### Project Structure Overview Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Provides a high-level overview of the project's directory structure, detailing the locations of the core library, tests, benchmarks, and samples. ```text Mapper/ src/ Mapper/ # core library (abstractions + expression engine) tests/ Mapper.Tests/ # unit + integration tests (EF Core in-memory provider) benchmarks/ Mapper.Benchmarks/ # BenchmarkDotNet benchmarks (run with dotnet run -c Release) samples/ Mapper.Samples/ # usage examples ``` -------------------------------- ### Customize Clone Mapper with Ignore and Map Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Demonstrates how to customize a clone mapper by ignoring specific properties or providing custom mapping logic for others, such as converting a collection to a list. ```csharp OrderClone = CreateCloneMapper() .Ignore(d => d.Id) .Map(d => d.Lines, s => s.Lines.ToList()); ``` -------------------------------- ### Combining Member-Init and Fluent Mapping Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Combine member-init expressions for simple properties with fluent .Map(), .Optional(), or .Ignore() calls for the rest. ```csharp Order = CreateMapper(src => new OrderDto { Id = src.Id, PlacedAt = src.CreatedAt, Lines = src.Lines.Project(OrderLine).ToList(), }) .Map(d => d.Status, s => s.Status.ToString()) .Optional(d => d.CustomerName, s => s.Customer.Name); ``` -------------------------------- ### Get Nullable Enum Mapping Expression Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Obtain the nullable enum mapping expression as an `Expression>` for use in LINQ projections. ```csharp Expression> expr = mapper.ToNullableExpression(); ``` -------------------------------- ### Get Nullable Enum Expression Tree Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Access the generated LINQ expression tree for nullable enum mappings, useful for advanced scenarios or analysis. ```csharp Expression> expr = mappers.OrderStatusMapper.ToNullableExpression(); ``` -------------------------------- ### Define MapperContext with Variables and Mappers Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Demonstrates how to create a custom MapperContext subclass, defining variables and mappers. Includes property-by-property mapping and member-initializer style mapping with optional properties and nested mapper reuse. ```csharp public class AppMappers : MapperContext { // Variables are real C# properties — discoverable, navigable, referenceable public Variable CurrentUserId { get; } public Mapper OrderLine { get; } public Mapper Order { get; } public AppMappers() { // Property-by-property style OrderLine = CreateMapper() .Map(dest => dest.ProductName, src => src.Product.Name) .Map(dest => dest.Quantity, src => src.Quantity) .Map(dest => dest.UnitPrice, src => src.UnitPrice); // Member-init expression passed directly to CreateMapper (optional) Order = CreateMapper(src => new OrderDto { Id = src.Id, PlacedAt = src.CreatedAt, Lines = OrderLine.Map(src.Lines), // nested mapper reuse IsOwner = src.OwnerId == CurrentUserId, }) .Optional(dest => dest.CustomerName, src => src.Customer.Name); // opt-in } } ``` -------------------------------- ### Use EagerBuildAll in Production Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/recommendations.md Call EagerBuildAll() in production to detect mapping configuration errors at startup. Ensure it's called last, after all mappers are assigned. ```csharp public class AppMappers : MapperContext { public AppMappers() { // ... mapper declarations ... EagerBuildAll(); // Always last — after all mappers are assigned } } ``` -------------------------------- ### Composing MapperContexts via Constructor Injection Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Illustrates how to compose multiple MapperContext subclasses into a larger context using standard C# constructor injection, without requiring specific library support. ```csharp public class AppMappers { public OrderMappers Orders { get; } public ProductMappers Products { get; } public AppMappers(OrderMappers orders, ProductMappers products) { Orders = orders; Products = products; } } ``` -------------------------------- ### Use Symmetric Enum Mapper for Forward and Reverse Mapping Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Demonstrates how to use the SymmetricEnumMapper for both forward (TLeft to TRight) and reverse (TRight to TLeft) mapping operations. ```csharp // Forward: TLeft → TRight OrderStatusDto dto = mappers.StatusMapper.Map(OrderStatus.Pending); // Reverse: TRight → TLeft OrderStatus domain = mappers.StatusMapper.MapReverse(OrderStatusDto.Shipped); ``` -------------------------------- ### Integrate Transformers with ASP.NET Core Options Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Configure GlobalMapperOptions using IOptions for standard ASP.NET Core configuration. Keeps the core library dependency-free. ```csharp // Registration — in Program.cs / Startup services.Configure(o => o.AddTransformer(new CastTransformer())); services.AddSingleton(); // Context — accepts IOptions<> and unwraps public class AppMappers : MapperContext { public AppMappers(IOptions options) : base(options.Value) { // ... } } ``` -------------------------------- ### Composing Multiple Mapper Contexts Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Illustrates how to compose multiple `MapperContext` subclasses into a larger unit using standard constructor injection. This pattern does not require specific library support for context organization. ```csharp public class AppMappers { public OrderMappers Orders { get; } public ProductMappers Products { get; } public AppMappers(OrderMappers orders, ProductMappers products) { Orders = orders; Products = products; } } ``` ```csharp builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); ``` -------------------------------- ### Use CreateCloneMapper for Identity Copies Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/recommendations.md To create a shallow copy of a model, use `CreateCloneMapper()` instead of manually listing properties. This auto-wired approach stays in sync with the model and avoids forgetting new properties. ```csharp // Good — auto-wired, stays in sync with the model OrderClone = CreateCloneMapper() .Ignore(d => d.Id); // Bad — manual listing that breaks when properties are added OrderClone = CreateMapper() .Map(d => d.Name, s => s.Name) .Map(d => d.Status, s => s.Status) // ... easy to forget new properties ``` -------------------------------- ### Property Mapping using Member-Init Expression Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Configure a mapping using a member-init expression, which is suitable for initializing multiple properties of the destination object. ```csharp CreateMapper(src => new OrderDto { Id = src.Id, PlacedAt = src.CreatedAt, }) ``` -------------------------------- ### Mapper Inheritance with Derived Source Types Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Demonstrates using `Inherit` when both source and destination types are derived. This allows reusing mappings from a base mapper for specific derived types. ```csharp TechnicalDetail = Inherit(Summary).For() .Map(dest => dest.Content, src => src.Content) .Map(dest => dest.CreatedAt, src => src.CreatedAt) .Map(dest => dest.Language, src => src.Language); ``` -------------------------------- ### Implementing an Expression Transformer Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Shows how to implement an `IExpressionTransformer` by inheriting from `ExpressionVisitor`. This allows rewriting mapper expression trees during compilation, useful for EF Core translation issues. ```csharp public class MethodCallTransformer : ExpressionVisitor, IExpressionTransformer { public Expression Transform(Expression expression) => Visit(expression); protected override Expression VisitMethodCall(MethodCallExpression node) { // Replace untranslatable method calls with EF Core-safe equivalents return base.VisitMethodCall(node); } } ``` -------------------------------- ### Request Optional Properties (String Notation) Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Use string paths with the Include method to request optional properties, which is useful when driven by external input. Paths are validated at call time. ```csharp query.Project(mapper.Order, o => o .Include("CustomerName") .Include("Lines.SupplierName")); ``` -------------------------------- ### Create a Composite Mapper Facade Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md An optional facade class that aggregates multiple MapperContext instances, providing a single entry point for all mappers. It uses constructor injection to receive the individual contexts. ```csharp public class CompositeMappers( PublisherMappers publishers, BookMappers books, AuthorMappers authors) { public PublisherMappers Publishers { get; } = publishers; public BookMappers Books { get; } = books; public AuthorMappers Authors { get; } = authors; } ``` -------------------------------- ### Repository Structure Overview Source: https://github.com/archpillar/extensions/blob/main/README.md This text-based diagram outlines the directory structure of the ArchPillar Extensions repository, detailing the location of source code, tests, benchmarks, samples, and documentation for each library. ```text ├── src/ │ ├── Mapper/ # Core mapping library │ ├── Mapper.EntityFrameworkCore/ # EF Core integration │ ├── Pipelines/ # Pipelines library (includes DI extensions) │ ├── Primitives/ # OperationResult / OperationProblem family │ └── Commands/ # In-process command dispatcher ├── tests/ │ ├── Mapper.Tests/ # Unit and integration tests │ ├── Mapper.OData.Tests/ # OData-specific tests │ ├── Pipelines.Tests/ # Pipelines unit + allocation + DI tests │ ├── Primitives.Tests/ # OperationResult / OperationProblem tests │ └── Commands.Tests/ # Dispatcher, validation, batch, telemetry tests ├── benchmarks/ │ ├── Mapper.Benchmarks/ # Mapper BenchmarkDotNet suite │ ├── Pipelines.Benchmarks/ # Pipelines BenchmarkDotNet suite │ └── Commands.Benchmarks/ # Commands BenchmarkDotNet suite ├── samples/ │ ├── Mapper/ │ │ ├── WebShop/ # ASP.NET Core Web API sample │ │ └── WebShop.OData/ # OData endpoint sample │ ├── Pipelines/ │ │ ├── Pipeline.BuilderSample/ # Direct (no-DI) Pipeline sample │ │ └── Pipeline.HostSample/ # Host-builder + AddPipeline() sample │ └── Commands/ │ ├── Command.HostSample/ # Host-builder dispatcher sample │ └── Command.WebApiSample/ # ASP.NET Core Minimal-API sample ├── docs/ │ ├── mapper/ # Mapper documentation and spec │ ├── pipelines/ # Pipelines documentation and spec │ ├── primitives/ # Primitives documentation │ └── commands/ # Commands documentation and spec ├── Directory.Build.props # Shared project properties ├── Directory.Build.targets # Roslyn analyzer configuration ├── Directory.Packages.props # Central package management └── ArchPillar.Extensions.slnx # Solution file ``` -------------------------------- ### Property Mapping using Fluent Style Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Configure a mapping property by property using a fluent API. This style is useful for explicitly defining each mapping relationship. ```csharp CreateMapper() .Map(dest => dest.Id, src => src.Id) .Map(dest => dest.PlacedAt, src => src.CreatedAt) ``` -------------------------------- ### Create Custom Mapper Context Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/getting-started.md Create a class inheriting from MapperContext to declare and initialize your mappers. Leaf mappers should be declared first for readability. ```csharp public class AppMappers : MapperContext { public Mapper OrderLine { get; } public Mapper Order { get; } public AppMappers() { OrderLine = CreateMapper(src => new OrderLineDto { ProductName = src.ProductName, Quantity = src.Quantity, UnitPrice = src.UnitPrice, }); Order = CreateMapper(src => new OrderDto { Id = src.Id, PlacedAt = src.CreatedAt, Status = src.Status, Lines = src.Lines.Project(OrderLine).ToList(), }); } } ``` -------------------------------- ### Perform Symmetric Enum Mapping (Forward and Reverse) Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Illustrates how to use a SymmetricEnumMapper for both forward (TLeft to TRight) and reverse (TRight to TLeft) enum value translations. ```csharp // Forward: OrderStatus → OrderStatusDto OrderStatusDto dto = mappers.StatusMapper.Map(OrderStatus.Pending); // Reverse: OrderStatusDto → OrderStatus OrderStatus domain = mappers.StatusMapper.MapReverse(OrderStatusDto.Shipped); ``` -------------------------------- ### MapperContext Abstract Class Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Defines the base context for mapper creation and configuration. It provides methods for creating different types of mappers and setting up global options. Use this as a base for custom mapper contexts. ```csharp public abstract class MapperContext { protected MapperContext(); protected MapperContext(GlobalMapperOptions? globalOptions); protected virtual CoverageValidation DefaultCoverageValidation => CoverageValidation.NonNullableProperties; protected static MapperBuilder CreateMapper( Expression>? memberInitExpression = null); protected InheritedMapperBuilder Inherit( Mapper baseMapper); protected static EnumMapper CreateEnumMapper( Func mappingMethod) where TSource : struct, Enum where TDest : struct, Enum; protected static SymmetricEnumMapper CreateSymmetricEnumMapper( Func forwardMethod) where TLeft : struct, Enum where TRight : struct, Enum; protected MapperBuilder CreateCloneMapper(); protected static Variable CreateVariable(string? name = null, T? defaultValue = default); protected void AddTransformer(IExpressionTransformer transformer); // Forces expression assembly and delegate compilation for every mapper // on this context. Call at the end of a subclass constructor to surface // errors at startup and eliminate cold-start latency. protected void EagerBuildAll(); } ``` -------------------------------- ### Member-Init Expression for Mapping Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Use a member-init expression to map straightforward properties. All properties assigned in the initializer are tracked as required mappings. ```csharp OrderLine = CreateMapper(src => new OrderLineDto { ProductName = src.ProductName, Quantity = src.Quantity, UnitPrice = src.UnitPrice, }); ``` -------------------------------- ### Requesting Optional Properties (String Path) Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Request optional properties using string paths with .Include(). Unknown paths throw an InvalidOperationException at call time. ```csharp .Project(mappers.Order, o => o .Include("CustomerName") .Include("Lines.SupplierName")) ``` -------------------------------- ### Requesting Optional Properties (Typed Lambda) Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Request optional properties using a typed lambda with .Include() when performing in-memory mapping. ```csharp var results = await dbContext.Orders .Project(mappers.Order, o => o .Include(m => m.CustomerName)) .ToListAsync(); ``` -------------------------------- ### Register Mappers as Singleton Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/recommendations.md Register your mapper instances as singletons to avoid redundant memory allocation and compilation time. Avoid transient registration. ```csharp builder.Services.AddSingleton(); ``` ```csharp // Not this builder.Services.AddTransient(); // Recompiles on every request ``` -------------------------------- ### Nested Mapper for Collections Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Map collections using the .Project(mapper) extension followed by a collection materializer like .ToList(). A null guard is emitted for optional properties mapped from a null source in memory. ```csharp Order = CreateMapper(src => new OrderDto { Lines = src.Lines.Project(OrderLine).ToList(), Tags = src.Tags.Project(TagMapper).ToHashSet(), }); ``` -------------------------------- ### Enabling ArchPillar Mapper Integration with EF Core Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Shows how to add EF Core query support using the `ArchPillar.Extensions.Mapper.EntityFrameworkCore` package. The `UseArchPillarMapper()` extension method is called on the `DbContextOptionsBuilder` to enable the integration. Mappers are resolved from constants in the query, and enum mapper tables are computed on demand. ```bash dotnet add package ArchPillar.Extensions.Mapper.EntityFrameworkCore ``` ```csharp // Program.cs builder.Services.AddDbContext(options => options .UseNpgsql(connectionString) .UseArchPillarMapper()); ``` -------------------------------- ### Basic MapTo Usage Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Use the MapTo method to assign mapped properties onto a pre-existing destination instance instead of creating a new one. This is suitable for updating entities or patching view-models. ```csharp // Basic mappers.Order.MapTo(command, existingOrder); // With variables mappers.Order.MapTo(command, existingOrder, o => o.Set(mappers.CurrentUserId, userId)); ``` -------------------------------- ### Direct Mapper Calls in Select Statement Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Use `Map()` for scalar properties and `Project()` for collection properties directly within a `Select` statement. The `IQueryExpressionInterceptor` translates these calls into efficient server-side expressions. ```csharp db.Orders.Select(o => new OrderRow { OrderId = o.Id, // hand-written Order = mappers.Order.Map(o), // scalar mapper call Lines = o.Lines.Project(mappers.OrderLine).ToList(), // collection mapper call }); ``` -------------------------------- ### ProjectionOptions Class Definition Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Defines options for server-side projection with IQueryable, allowing includes and variable settings. ```csharp public sealed class ProjectionOptions { public ProjectionOptions Include(Expression> optionalProp); public ProjectionOptions Include(string path); public ProjectionOptions Set(Variable variable, T value); } ``` -------------------------------- ### Mapper Performance Benchmark Results Table Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/README.md A table summarizing the performance of different mapping methods. It includes Mean, Error, StdDev, Gen0, and Allocated metrics for each method. ```markdown | Method | Mean | Error | StdDev | Gen0 | Allocated | |-------------------------------------- |-----------:|------------:|----------:|-------:|----------:| | Empty→Empty | 5.113 ns | 5.1559 ns | 0.2826 ns | 0.0014 | 24 B | | '1 property' | 5.956 ns | 4.7004 ns | 0.2576 ns | 0.0014 | 24 B | | '5 properties' | 8.874 ns | 6.2752 ns | 0.3440 ns | 0.0029 | 48 B | | '10 properties' | 17.087 ns | 9.3984 ns | 0.5152 ns | 0.0048 | 80 B | | 'Map: 1 variable' | 35.195 ns | 56.1062 ns | 3.0754 ns | 0.0153 | 256 B | | 'Map: 5 variables' | 90.270 ns | 132.2053 ns | 7.2466 ns | 0.0300 | 504 B | | 'Map: 10 variables' | 175.951 ns | 22.0808 ns | 1.2103 ns | 0.0544 | 912 B | | 'MapTo: 1 variable' | 27.672 ns | 1.4650 ns | 0.0803 ns | 0.0138 | 232 B | | 'MapTo: 5 variables' | 102.566 ns | 81.4480 ns | 4.4644 ns | 0.0286 | 480 B | | 'MapTo: 10 variables' | 159.800 ns | 41.5725 ns | 2.2787 ns | 0.0525 | 880 B | | 'MapTo: 5 properties' | 4.452 ns | 0.7323 ns | 0.0401 ns | - | - | | 'MapTo: 10 properties' | 7.018 ns | 7.0556 ns | 0.3867 ns | - | - | | 'MapTo: List (1 item)' | 37.802 ns | 29.7460 ns | 1.6305 ns | 0.0095 | 160 B | | 'MapTo: 5-level nesting' | 23.557 ns | 28.6322 ns | 1.5694 ns | 0.0072 | 120 B | | 'Nested (empty objects)' | 8.305 ns | 7.1202 ns | 0.3903 ns | 0.0029 | 48 B | | '5-level nesting' | 25.058 ns | 44.0275 ns | 2.4133 ns | 0.0091 | 152 B | | '10-level nesting' | 46.549 ns | 9.0525 ns | 0.4962 ns | 0.0186 | 312 B | | 'List (1 item)' | 57.999 ns | 73.6764 ns | 4.0385 ns | 0.0110 | 184 B | | 'T[] (1 item)' | 48.947 ns | 12.7049 ns | 0.6964 ns | 0.0076 | 128 B | | 'HashSet (1 item)' | 164.377 ns | 46.0255 ns | 2.5228 ns | 0.0191 | 320 B | | 'Dictionary (1 item)' | 45.459 ns | 32.3739 ns | 1.7745 ns | 0.0157 | 264 B | | 'Baseline: new List (1 item)' | 14.965 ns | 6.9183 ns | 0.3792 ns | 0.0052 | 88 B | | 'Baseline: Select+ToList (1 item)' | 32.091 ns | 48.4177 ns | 2.6539 ns | 0.0095 | 160 B | | 'Baseline: Select+ToArray (1 item)' | 36.390 ns | 16.9963 ns | 0.9316 ns | 0.0062 | 104 B | | 'Baseline: Select+ToHashSet (1 item)' | 143.512 ns | 67.2331 ns | 3.6853 ns | 0.0176 | 296 B | ``` -------------------------------- ### LINQ Projection Expression Structure Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Illustrates the structure of a `MemberInitExpression` used for LINQ projections, showing how properties are mapped and optional properties are handled. ```csharp src => new TDest { Prop1 = , Prop2 = , ... OptionalProp = } ``` -------------------------------- ### Customizing a Clone Mapper Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Customize clone mappers by ignoring specific properties or providing custom mapping logic for others, such as deep-copying collections. ```csharp OrderClone = CreateCloneMapper() .Ignore(d => d.Id) // Don't copy the primary key .Map(d => d.Lines, s => s.Lines.ToList()); // Deep-copy the collection ``` -------------------------------- ### Invoke Nested Mapper (Opt-out of Inlining) Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Call Invoke(src.X) instead of Map(src.X) when a nested mapping cannot or should not be translated to SQL. This invokes the mapper on the materialized source. ```csharp Order = CreateMapper(src => new OrderDto { Form = FormMapper.Invoke(src.Form), // NOT inlined; the mapper is invoked on the materialised source }) ``` -------------------------------- ### Variable Implicit Operator Explanation Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Explains how the implicit operator for Variable allows seamless integration into expressions. ```csharp The implicit operator allows `Variable` to appear directly in expression bodies (e.g., `src.OwnerId == CurrentUserId`) without casting. The `ExpressionVisitor` replaces occurrences of the variable's node with a `ConstantExpression` at call time. ``` -------------------------------- ### Fluent Property-by-Property Mapping Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Chain .Map() calls on the builder for property-by-property mapping. Each call maps a single destination property from a source expression. ```csharp OrderLine = CreateMapper() .Map(d => d.ProductName, s => s.ProductName) .Map(d => d.Quantity, s => s.Quantity) .Map(d => d.UnitPrice, s => s.UnitPrice); ``` -------------------------------- ### Declaring a Clone Mapper Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Create a clone mapper for a specific type to automatically map all public settable properties. This is useful for creating shallow copies of objects. ```csharp public class AppMappers : MapperContext { public Mapper OrderClone { get; } public AppMappers() { OrderClone = CreateCloneMapper(); } } ``` -------------------------------- ### Symmetric Enum Mapper Nullable Support Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Illustrates the nullable support for SymmetricEnumMapper, including mapping nullable enums to nullable enums and mapping nullable enums to default values. ```csharp | Call | Return | |------|--------| | `Map(TLeft)` / `MapReverse(TRight)` | `TRight` / `TLeft` | | `Map(TLeft?)` / `MapReverse(TRight?)` | `TRight?` / `TLeft?` | | `Map(TLeft?, TRight default)` / `MapReverse(TRight?, TLeft default)` | `TRight` / `TLeft` | ``` -------------------------------- ### Define Source and Destination Models Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/getting-started.md Define the source data models (Order, OrderLine) and their corresponding destination Data Transfer Objects (DTOs) (OrderDto, OrderLineDto). ```csharp using ArchPillar.Extensions.Mapper; // Source model public class Order { public int Id { get; set; } public DateTime CreatedAt { get; set; } public string Status { get; set; } public List Lines { get; set; } } public class OrderLine { public string ProductName { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } } // Destination DTOs public class OrderDto { public required int Id { get; set; } public required DateTime PlacedAt { get; set; } public required string Status { get; set; } public required List Lines { get; set; } } public class OrderLineDto { public required string ProductName { get; set; } public required int Quantity { get; set; } public required decimal UnitPrice { get; set; } } ``` -------------------------------- ### Request Optional Properties (Typed Lambda) Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Explicitly request optional properties using a typed lambda with the Include method. This is compile-time safe and IDE-navigable. ```csharp query.Project(mapper.Order, o => o .Include(m => m.CustomerName) .Include(m => m.Lines, line => line.Include(l => l.SupplierName))); ``` -------------------------------- ### Test Order Projection with EF Core InMemory Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/recommendations.md Tests EF Core projection using the in-memory database provider. This verifies that the mapper can be used within an EF Core query pipeline for projections. ```csharp using Xunit; using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.Threading.Tasks; public class MapperTests { [Fact] public async Task Order_Project_TranslatesToQuery() { using var context = CreateInMemoryDb(); var mappers = new AppMappers(); List results = await context.Orders .Project(mappers.Order) .ToListAsync(); Assert.NotEmpty(results); } // Assume CreateInMemoryDb() is defined elsewhere to set up the DbContext private DbContext CreateInMemoryDb() { return null; } // Placeholder } ``` -------------------------------- ### Mapper Calls with Projection Options Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Include specific properties in mapper calls using `ProjectionOptions` overloads for optional properties. This allows for selective mapping within queries. ```csharp mappers.Order.Map(o, c => c.Include(m => m.CustomerName)); o.Lines.Project(mappers.OrderLine, c => c.Include(m => m.SupplierName)); ``` -------------------------------- ### MapTo Existing Object Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Use MapTo to assign mapped properties onto a pre-existing destination instance. This is useful for updating tracked entities or DTOs in place. ```csharp // Update an existing tracked entity from a command object mapper.Order.MapTo(command, existingOrder); // With a runtime variable mapper.Order.MapTo(command, existingOrder, o => o.Set(mapper.CurrentUserId, userId)); ``` -------------------------------- ### Test Order Mapping to Expression Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/recommendations.md Tests the conversion of Order mapping to an expression. This is useful for LINQ to Objects or other expression-based scenarios. The expression is compiled and then executed. ```csharp using Xunit; public class MapperTests { [Fact] public void Order_ToExpression_ProducesValidExpression() { var mappers = new AppMappers(); var expression = mappers.Order.ToExpression(); Func compiled = expression.Compile(); OrderDto result = compiled(new Order { Id = 1, Status = "Active" }); Assert.Equal(1, result.Id); } } ``` -------------------------------- ### Use Optionals for Expensive Joins Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/recommendations.md Mark navigation properties that trigger additional SQL joins as optional to defer loading until explicitly requested. This improves performance for list endpoints. ```csharp Order = CreateMapper(src => new OrderDto { Id = src.Id, Status = src.Status.ToString(), Lines = src.Lines.Project(OrderLine).ToList(), }) .Optional(d => d.CustomerName, s => s.Customer.Name); // Extra join ``` ```csharp // List — no Customer join var list = await context.Orders.Project(mappers.Order).ToListAsync(); // Detail — with Customer join var detail = await context.Orders .Where(o => o.Id == id) .Project(mappers.Order, o => o.Include(m => m.CustomerName)) .FirstAsync(); ``` -------------------------------- ### MapOptions Class Definition Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Defines options for mapping operations on IEnumerable collections. ```csharp public sealed class MapOptions { public MapOptions Set(Variable variable, T value); } ``` -------------------------------- ### Use Inherit for Destination Type Hierarchies Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/recommendations.md When mapping to a base DTO and its derived DTOs, use `Inherit()` to avoid duplicating property mappings. This ensures shared mappings are defined once and reduces maintenance overhead. ```csharp // Good — shared mappings are defined once Summary = CreateMapper(src => new DocumentSummaryDto { Id = src.Id, Title = src.Title, }); Detail = Inherit(Summary).For() .Map(dest => dest.Content, src => src.Content); // Bad — duplicated mappings drift apart over time Detail = CreateMapper(src => new DocumentDetailDto { Id = src.Id, // duplicated Title = src.Title, // duplicated Content = src.Content, }); ``` -------------------------------- ### Register Mapper Context with Dependency Injection Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/getting-started.md Register the custom mapper context as a singleton service in your application's dependency injection container. ```csharp // Program.cs builder.Services.AddSingleton(); ``` -------------------------------- ### MapperExtensions Class Definition Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Defines extension methods for projecting collections and IQueryable objects using a provided mapper. ```csharp public static class MapperExtensions { // IQueryable — server-side projection (EF Core) public static IQueryable Project( this IQueryable query, Mapper mapper, Action>? options = null); // IEnumerable — expression-safe (no optional params), for use inside // member-init expressions; caller chooses output collection via ToList() etc. public static IEnumerable Project( this IEnumerable source, Mapper mapper); // IEnumerable — standalone use with options public static IEnumerable Project( this IEnumerable source, Mapper mapper, Action> options); } ``` -------------------------------- ### Projection within Member-Init Expression Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Illustrates using collection projection within a C# member-initializer expression for creating DTOs. ```csharp // Inside a parent mapper member-init expression Order = CreateMapper(src => new OrderDto { Lines = src.Lines.Project(OrderLine).ToList(), Tags = src.Tags.Project(TagMapper).ToHashSet(), Customer = CustomerMapper.Map(src.Customer), // single object LookupById = src.Items.ToDictionary(i => i.Id, i => Item.Map(i)), // dictionary }); ``` -------------------------------- ### Inline Initializers for Nested Mappers Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Nested new { ... } initializers within a parent mapper expression are supported, with mapper calls inside them inlined correctly. ```csharp Shipment = CreateMapper(s => new ShipmentDest { Id = s.Id, Pack = new PackDest { Primary = PartMapper.Map(s.Pack.Primary), Secondary = PartMapper.Map(s.Pack.Secondary), }, }); ``` -------------------------------- ### GlobalMapperOptions Class Definition Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Manages global mapper options, including a list of expression transformers. ```csharp public sealed class GlobalMapperOptions { public IReadOnlyList Transformers { get; } public GlobalMapperOptions AddTransformer(IExpressionTransformer transformer); } ``` -------------------------------- ### Perform EF Core Projection Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/README.md Utilize the mappers for efficient EF Core projections. This allows LINQ queries to be translated into SQL, avoiding client-side evaluation and N+1 query problems. Runtime variables can also be applied. ```csharp var results = await dbContext.Orders .Where(o => o.IsActive) .Project(mappers.Order, o => o .Include(m => m.CustomerName) .Set(mappers.CurrentUserId, currentUser.Id)) .ToListAsync(); ``` -------------------------------- ### Define a Leaf MapperContext Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Create a base MapperContext that has no external dependencies. This context defines mappers for a specific entity, like Publisher. ```csharp public class PublisherMappers : MapperContext { public Mapper Publisher { get; } public PublisherMappers() { Publisher = CreateMapper(src => new PublisherDto { Id = src.Id, Name = src.Name, }) .Optional(dest => dest.Country, src => src.Country); } } ``` -------------------------------- ### Use Enum Mapping Standalone Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Perform a direct mapping between enum values using a pre-defined EnumMapper instance. ```csharp OrderStatusDto mapped = mappers.OrderStatusMapper.Map(OrderStatus.Pending); ``` -------------------------------- ### Perform In-Memory Mapping Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/README.md Execute in-memory object mapping using the defined mappers. You can also provide runtime variables, such as a user ID, to influence the mapping process. ```csharp OrderDto dto = mappers.Order.Map(order); // With a runtime variable OrderDto dto = mappers.Order.Map(order, o => o.Set(mappers.CurrentUserId, userId)); ``` -------------------------------- ### Define a Deeper Chained MapperContext Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Illustrates a three-level dependency chain where AuthorMappers depends on BookMappers, which in turn depends on PublisherMappers. Nested mapper calls are inlined. ```csharp public class AuthorMappers : MapperContext { public Mapper Author { get; } public AuthorMappers(BookMappers bookMappers) { Author = CreateMapper(src => new AuthorDto { Id = src.Id, Name = src.Name, }) .Optional(dest => dest.Books, src => src.Books.Project(bookMappers.Book).ToList()); } } ``` -------------------------------- ### Custom Expression Transformer Implementation Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/SPEC.md Implement IExpressionTransformer to rewrite mapper expression trees during compilation. Useful for EF Core translation. ```csharp public class CastTransformer : ExpressionVisitor, IExpressionTransformer { public Expression Transform(Expression expression) => Visit(expression); protected override Expression VisitUnary(UnaryExpression node) { // Replace implicit conversions with explicit casts, etc. return base.VisitUnary(node); } } ``` -------------------------------- ### Handle Nullable Enums in Mappers Source: https://github.com/archpillar/extensions/blob/main/docs/mapper/features.md Demonstrates mapping nullable enum source values to nullable or non-nullable destination types, including handling nulls with default values. ```csharp Order = CreateMapper(src => new OrderDto { // Nullable → nullable: null in → null out Status = OrderStatusMapper.Map(src.NullableStatus), // Nullable → non-nullable with default: null in → default value out Priority = PriorityMapper.Map(src.NullablePriority, PriorityDto.Normal), // Non-nullable → nullable: uses C#'s implicit conversion (no special handling) Category = CategoryMapper.Map(src.Category), }); ```