Facet.Extensions 2.4.5

dotnet add package Facet.Extensions --version 2.4.5
                    
NuGet\Install-Package Facet.Extensions -Version 2.4.5
                    
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" Version="2.4.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Facet.Extensions" Version="2.4.5" />
                    
Directory.Packages.props
<PackageReference Include="Facet.Extensions" />
                    
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 --version 2.4.5
                    
#r "nuget: Facet.Extensions, 2.4.5"
                    
#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@2.4.5
                    
#: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&version=2.4.5
                    
Install as a Cake Addin
#tool nuget:?package=Facet.Extensions&version=2.4.5
                    
Install as a Cake Tool

<div align="center"> <img src="https://raw.githubusercontent.com/Tim-Maes/Facet/master/assets/Facet.png" alt="Facet logo" width="400"> </div>

<div align="center"> "One part of a subject, situation, object that has many parts." </div>


Facet is a C# source generator that lets you define lightweight projections (DTOs, API models, etc.) directly from your domain models, without writing boilerplate.

It generates partial classes, records, structs, or record structs with constructors, optional LINQ projections, and even supports custom mappings, all at compile time, with zero runtime cost.

💎 What is Facetting?

Facetting is the process of defining focused views of a larger model at compile time.

Instead of manually writing separate DTOs, mappers, and projections, Facet allows you to declare what you want to keep, and generates everything else.

You can think of it like carving out a specific facet of a gem:

  • The part you care about
  • Leaving the rest behind.

❔ Why Facetting?

  • Reduce duplication across DTOs, projections, and ViewModels
  • Maintain strong typing with no runtime cost
  • Stay DRY (Don't Repeat Yourself) without sacrificing performance
  • Works seamlessly with LINQ providers like Entity Framework

📋 Documentation

⭐ Key Features

  • ✅ Generate classes, records, structs, or record structs from existing types
  • ✅ Exclude fields/properties you don't want (create a Facetted view of your model)
  • ✅ Include/redact public fields
  • ✅ Auto-generate constructors for fast mapping
  • ✅ LINQ projection expressions
  • ✅ Full mapping support with custom mapping configurations

🌎 The Facet Ecosystem

Facet is modular and consists of several NuGet packages:

  • Facet: The core source generator. Generates DTOs, projections, and mapping code.

  • Facet.Extensions: Provider-agnostic extension methods for mapping and projecting (works with any LINQ provider, no EF Core dependency).

  • Facet.Mapping: Advanced static mapping configuration support with async capabilities and dependency injection for complex mapping scenarios.

  • Facet.Extensions.EFCore: Async extension methods for Entity Framework Core (requires EF Core 6+).

🚀 Quick start

Install the NuGet Package

dotnet add package Facet

For LINQ helpers:

dotnet add package Facet.Extensions

For EF Core support:

dotnet add package Facet.Extensions.EFCore

Basic Projection

[Facet(typeof(User))]
public partial class UserFacet { }

// Auto-generates constructor, properties, and LINQ projection
var user = user.ToFacet<UserFacet>();
var users = users.SelectFacets<UserFacet>();

Property Exclusion & Field Inclusion

// Exclude sensitive properties
string[] excludeFields = { "Password", "Email" };

[Facet(typeof(User), exclude: excludeFields)]
public partial class UserWithoutEmail { }

// Include public fields
[Facet(typeof(Entity), IncludeFields = true)]
public partial class EntityDto { }

Different Type Kinds

// Generate as record (immutable by default)
[Facet(typeof(Product))]
public partial record ProductDto;

// Generate as struct (value type)
[Facet(typeof(Point))]
public partial struct PointDto;

// Generate as record struct (immutable value type)
[Facet(typeof(Coordinates))]
public partial record struct CoordinatesDto; // Preserves required/init-only

Custom Sync Mapping

public class UserMapper : IFacetMapConfiguration<User, UserDto>
{
    public static void Map(User source, UserDto target)
    {
        target.FullName = $"{source.FirstName} {source.LastName}";
        target.Age = CalculateAge(source.DateOfBirth);
    }
}

[Facet(typeof(User), Configuration = typeof(UserMapper))]
public partial class UserDto 
{
    public string FullName { get; set; }
    public int Age { get; set; }
}

Async Mapping for I/O Operations

public class UserAsyncMapper : IFacetMapConfigurationAsync<User, UserDto>
{
    public static async Task MapAsync(User source, UserDto target, CancellationToken cancellationToken = default)
    {
        // Async database lookup
        target.ProfilePicture = await GetProfilePictureAsync(source.Id, cancellationToken);
        
        // Async API call
        target.ReputationScore = await CalculateReputationAsync(source.Email, cancellationToken);
    }
}

// Usage
var userDto = await user.ToFacetAsync<User, UserDto, UserAsyncMapper>();
var userDtos = await users.ToFacetsParallelAsync<User, UserDto, UserAsyncMapper>();

Async Mapping with Dependency Injection

public class UserAsyncMapperWithDI : IFacetMapConfigurationAsyncInstance<User, UserDto>
{
    private readonly IProfilePictureService _profileService;
    private readonly IReputationService _reputationService;

    public UserAsyncMapperWithDI(IProfilePictureService profileService, IReputationService reputationService)
    {
        _profileService = profileService;
        _reputationService = reputationService;
    }

    public async Task MapAsync(User source, UserDto target, CancellationToken cancellationToken = default)
    {
        // Use injected services
        target.ProfilePicture = await _profileService.GetProfilePictureAsync(source.Id, cancellationToken);
        target.ReputationScore = await _reputationService.CalculateReputationAsync(source.Email, cancellationToken);
    }
}

// Usage with DI
var mapper = new UserAsyncMapperWithDI(profileService, reputationService);
var userDto = await user.ToFacetAsync(mapper);
var userDtos = await users.ToFacetsParallelAsync(mapper);

EF Core Integration

Forward Mapping (Entity → Facet)
// Async projection directly in EF Core queries
var userDtos = await dbContext.Users
    .Where(u => u.IsActive)
    .ToFacetsAsync<UserDto>();

// LINQ projection for complex queries
var results = await dbContext.Products
    .Where(p => p.IsAvailable)
    .SelectFacet<ProductDto>()
    .OrderBy(dto => dto.Name)
    .ToListAsync();
Reverse Mapping (Facet → Entity)
[Facet(typeof(User)]
public partial class UpdateUserDto { }

[HttpPut("{id}")]
public async Task<IActionResult> UpdateUser(int id, UpdateUserDto dto)
{
    var user = await context.Users.FindAsync(id);
    if (user == null) return NotFound();
    
    // Only updates properties that mutated
    user.UpdateFromFacet(dto, context);
    
    await context.SaveChangesAsync();
    return NoContent();
}

// With change tracking for auditing
var result = user.UpdateFromFacetWithChanges(dto, context);
if (result.HasChanges)
{
    logger.LogInformation("User {UserId} updated. Changed: {Properties}", 
        user.Id, string.Join(", ", result.ChangedProperties));
}

📈 Performance Benchmarks

Facet delivers competitive performance across different mapping scenarios. Here's how it compares to popular alternatives:

Single Mapping

Library Mean Time Memory Allocated Performance vs Facet
Facet 15.93 ns 136 B Baseline
Mapperly 15.09 ns 128 B 5% faster, 6% less memory
Mapster 21.90 ns 128 B 38% slower, 6% less memory

Collection Mapping (10 items)

Library Mean Time Memory Allocated Performance vs Facet
Mapster 192.55 ns 1,416 B 10% faster, 10% less memory
Facet 207.32 ns 1,568 B Baseline
Mapperly 222.50 ns 1,552 B 7% slower, 1% less memory

Insights:

  • Single mapping: All three libraries perform similarly with sub-nanosecond differences
  • Collection mapping: Mapster has a slight edge for bulk operations, while Facet and Mapperly are very close
  • Memory efficiency: All libraries are within ~10% of each other for memory allocation
  • Compile-time generation: Both Facet and Mapperly benefit from zero-runtime-cost source generation
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 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Facet.Extensions:

Package Downloads
Facet.Extensions.EFCore

EF Core async extension methods for Facet.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.4.5 23 8/30/2025
2.4.4 77 8/27/2025
2.4.3 63 8/27/2025
2.4.2 64 8/27/2025
2.4.0 67 8/26/2025
2.3.0 859 8/20/2025
2.2.0 147 8/20/2025
2.1.0 248 8/18/2025
2.0.1 471 8/5/2025
2.0.0 141 8/4/2025
1.9.3 125 7/4/2025
1.8.0 153 6/4/2025
1.7.0 163 5/6/2025
1.6.0 143 4/27/2025
1.5.0 164 4/27/2025