Facet.Extensions.EFCore.Mapping 5.0.0-alpha

This is a prerelease version of Facet.Extensions.EFCore.Mapping.
There is a newer version of this package available.
See the version list below for details.
dotnet add package Facet.Extensions.EFCore.Mapping --version 5.0.0-alpha
                    
NuGet\Install-Package Facet.Extensions.EFCore.Mapping -Version 5.0.0-alpha
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Facet.Extensions.EFCore.Mapping" Version="5.0.0-alpha" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Facet.Extensions.EFCore.Mapping" Version="5.0.0-alpha" />
                    
Directory.Packages.props
<PackageReference Include="Facet.Extensions.EFCore.Mapping" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Facet.Extensions.EFCore.Mapping --version 5.0.0-alpha
                    
#r "nuget: Facet.Extensions.EFCore.Mapping, 5.0.0-alpha"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Facet.Extensions.EFCore.Mapping@5.0.0-alpha
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Facet.Extensions.EFCore.Mapping&version=5.0.0-alpha&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Facet.Extensions.EFCore.Mapping&version=5.0.0-alpha&prerelease
                    
Install as a Cake Tool

Facet.Extensions.EFCore.Mapping

Advanced custom async mapper support for Facet with EF Core queries. This package enables complex mappings that cannot be expressed as SQL projections, such as calling external services, complex type conversions (like Vector2), or async operations.

When to Use This Package

Use this package when you need to:

  • Call external services during mapping (e.g., geocoding, user lookup)
  • Perform complex type conversions that can't be translated to SQL
  • Execute async operations as part of the mapping process
  • Apply custom business logic that requires dependency injection

For simple projection-based mappings, use Facet.Extensions.EFCore instead.

Getting Started

1. Install packages

dotnet add package Facet.Extensions.EFCore.Mapping

This package automatically includes:

  • Facet.Extensions.EFCore - Core EF Core extensions
  • Facet.Mapping - Custom mapper interfaces

2. Import namespaces

using Facet.Extensions.EFCore.Mapping;  // For custom mapper methods
using Facet.Mapping;                     // For mapper interfaces

Usage Examples

Example 1: Simple Type Conversion (Vector2)

This addresses the GitHub issue #134 - converting separate X, Y properties into a Vector2 type.

// Domain entity
public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public decimal X { get; set; }
    public decimal Y { get; set; }
}

// DTO with custom mapping
[Facet(typeof(User), exclude: ["X", "Y"])]
public partial class UserDto
{
    public Vector2 Position { get; set; }
}

// Static mapper (no DI needed)
public class UserMapper : IFacetMapConfigurationAsync<User, UserDto>
{
    public static async Task MapAsync(User source, UserDto target, CancellationToken cancellationToken = default)
    {
        target.Position = new Vector2(source.X, source.Y);
        await Task.CompletedTask; // Or actual async work
    }
}

// Usage
var users = await dbContext.Users
    .Where(u => u.IsActive)
    .ToFacetsAsync<User, UserDto, UserMapper>();

var user = await dbContext.Users
    .Where(u => u.Id == userId)
    .FirstFacetAsync<User, UserDto, UserMapper>();

Example 2: Dependency Injection with External Services

// DTO
[Facet(typeof(User), exclude: ["LocationId"])]
public partial class UserDto
{
    public string Location { get; set; }
}

// Instance mapper with DI
public class UserMapper : IFacetMapConfigurationAsyncInstance<User, UserDto>
{
    private readonly ILocationService _locationService;

    public UserMapper(ILocationService locationService)
    {
        _locationService = locationService;
    }

    public async Task MapAsync(User source, UserDto target, CancellationToken cancellationToken = default)
    {
        // Call external service to resolve location name
        target.Location = await _locationService.GetLocationAsync(source.LocationId, cancellationToken);
    }
}

// Register in DI container
services.AddScoped<UserMapper>();

// Usage in controller
public class UsersController : ControllerBase
{
    private readonly ApplicationDbContext _context;
    private readonly UserMapper _userMapper;

    public UsersController(ApplicationDbContext context, UserMapper userMapper)
    {
        _context = context;
        _userMapper = userMapper;
    }

    [HttpGet]
    public async Task<ActionResult<List<UserDto>>> GetUsers()
    {
        return await _context.Users
            .Where(u => u.IsActive)
            .ToFacetsAsync<User, UserDto>(_userMapper);
    }
}

Example 3: Complex Business Logic

// DTO
[Facet(typeof(Order))]
public partial class OrderDto
{
    public string ShippingEstimate { get; set; }
    public decimal TotalWithTax { get; set; }
}

// Mapper with multiple services
public class OrderMapper : IFacetMapConfigurationAsyncInstance<Order, OrderDto>
{
    private readonly IShippingService _shippingService;
    private readonly ITaxCalculator _taxCalculator;

    public OrderMapper(IShippingService shippingService, ITaxCalculator taxCalculator)
    {
        _shippingService = shippingService;
        _taxCalculator = taxCalculator;
    }

    public async Task MapAsync(Order source, OrderDto target, CancellationToken cancellationToken = default)
    {
        // Calculate shipping estimate
        var shippingDays = await _shippingService.EstimateDeliveryAsync(
            source.ShippingAddress,
            cancellationToken);
        target.ShippingEstimate = $"{shippingDays} business days";

        // Calculate tax
        var tax = await _taxCalculator.CalculateTaxAsync(
            source.Total,
            source.ShippingAddress.State,
            cancellationToken);
        target.TotalWithTax = source.Total + tax;
    }
}

API Reference

Method Description
ToFacetsAsync<TSource, TTarget>(mapper) Project query to list with instance mapper (DI support)
ToFacetsAsync<TSource, TTarget, TAsyncMapper>() Project query to list with static async mapper
FirstFacetAsync<TSource, TTarget>(mapper) Get first with instance mapper (DI support)
FirstFacetAsync<TSource, TTarget, TAsyncMapper>() Get first with static async mapper
SingleFacetAsync<TSource, TTarget>(mapper) Get single with instance mapper (DI support)
SingleFacetAsync<TSource, TTarget, TAsyncMapper>() Get single with static async mapper

How It Works

  1. Query Execution: The EF Core query is executed first, materializing entities from the database
  2. Auto-Mapping: All matching properties are automatically mapped (like the base Facet behavior)
  3. Custom Mapping: Your custom mapper is called to handle additional logic

Important: These methods execute the SQL query first, then apply custom mapping. This means:

  • ✅ You can use async operations, external services, and complex logic
  • ⚠️ The query is materialized before custom mapping (can't be translated to SQL)
  • 💡 Use standard ToFacetsAsync from Facet.Extensions.EFCore if you only need SQL projections

Mapper Interfaces

Static Mapper (No DI)

public class MyMapper : IFacetMapConfigurationAsync<TSource, TTarget>
{
    public static async Task MapAsync(TSource source, TTarget target, CancellationToken cancellationToken = default)
    {
        // Custom mapping logic
    }
}

Instance Mapper (With DI)

public class MyMapper : IFacetMapConfigurationAsyncInstance<TSource, TTarget>
{
    private readonly IMyService _service;

    public MyMapper(IMyService service)
    {
        _service = service;
    }

    public async Task MapAsync(TSource source, TTarget target, CancellationToken cancellationToken = default)
    {
        // Custom mapping logic with injected services
    }
}

Requirements

  • Facet v1.6.0+
  • Facet.Extensions.EFCore v1.0.0+
  • Facet.Mapping v1.0.0+
  • Entity Framework Core 6+
  • .NET 6+
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
5.2.0 213 12/23/2025
5.2.0-alpha 266 12/18/2025
5.1.12 285 12/14/2025
5.1.11 460 12/9/2025
5.1.10 248 12/5/2025
5.1.9 662 12/3/2025
5.1.8 672 12/1/2025
5.1.7 487 12/1/2025
5.1.6 112 11/28/2025
5.1.5 111 11/28/2025
5.1.4 130 11/28/2025
5.1.3 143 11/28/2025
5.1.2 176 11/27/2025
5.1.1 166 11/27/2025
5.1.0 164 11/27/2025
5.0.4 179 11/26/2025
5.0.3 169 11/26/2025
5.0.2 183 11/26/2025
5.0.1 186 11/25/2025
5.0.0 178 11/24/2025
5.0.0-alpha5 182 11/24/2025
5.0.0-alpha4 152 11/23/2025
5.0.0-alpha3 170 11/22/2025
5.0.0-alpha2 170 11/22/2025
5.0.0-alpha 258 11/21/2025
4.4.3 303 11/21/2025
4.4.2 331 11/21/2025
4.4.1 386 11/20/2025
4.4.1-alpha4 268 11/16/2025
4.4.1-alpha3 262 11/16/2025
4.4.1-alpha2 269 11/16/2025
4.4.1-alpha 272 11/16/2025
4.4.0 227 11/15/2025
4.4.0-alpha 211 11/14/2025
4.3.3.1 209 11/14/2025
4.3.3 211 11/14/2025
4.3.2.1 211 11/14/2025
4.3.2 281 11/13/2025
4.3.1 274 11/12/2025
4.3.0 123 11/8/2025
4.3.0-alpha 132 11/7/2025
3.4.0 113 11/8/2025
3.3.0 141 11/7/2025
1.0.0 179 11/27/2025