Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
F

Facet

> 编程语言
Open source

Generate DTOs, mappings, constructors and LINQ projections from your domain models.

1.2K stars0 likes0 views
WebsiteGitHub

About

Generate DTOs, mappings, constructors and LINQ projections from your domain models.


Facet is a C# source generator that eliminates DTO boilerplate. Declare what you want, and Facet generates the type, constructor, LINQ projection, and reverse mapping, all at compile time with zero runtime overhead.

:gem: What is a Facet?

Think of your domain model as a gem with many facets! Different views for different purposes:

  • Public APIs need a facet without sensitive data
  • Admin endpoints need a different facet with additional fields
  • Database queries need efficient projections

Instead of manually creating each facet, Facet auto-generates them from a single source of truth. Generates constructors, projections, reverse mappings, patch, etc...

:clipboard: Documentation

  • Documentation & Guides
  • Facet Dashboard
  • What is being generated?
  • Configure generated files output location
  • Global configuration defaults - Override attribute defaults project-wide
  • Changelog

:handshake: Community

  • Contributing Guide
  • Code of Conduct
  • Governance
  • Security Policy
  • Support Guide

:star: Features

Code Generation

  • Generate DTOs as classes, records, structs, or record structs
  • Constructor, static factory, and LINQ projection expression, all generated
  • Nested objects and collections mapped automatically
  • Preserves XML documentation and data validation attributes

Mapping & Customization

  • Sync and async custom mapping configurations (static or DI-resolved instances)
  • Custom reverse mapping config to map back to source
  • Include/exclude properties with simple attribute arguments
  • Global Configuration - override default attribute settings project-wide via MSBuild properties (docs)
  • [MapFrom] - declarative property renaming with optional reverse mapping and expression support
  • [MapWhen] - conditional mapping based on runtime values, works in SQL projections
  • Before/After hooks - inject validation, defaults, or computed values around auto-mapping
  • ConvertEnumsTo - convert all enums to string or int with full round-trip support
  • CollectionTargetType - remap source collection types (e.g. Collection) to List or any target collection type globally per facet; per-property via [MapFrom(..., AsCollection = typeof(List<>))]
  • GenerateCopyConstructor - generate a copy constructor for cloning and MVVM scenarios
  • GenerateEquality - generate value-based Equals, GetHashCode, ==, != for class DTOs
  • SetAccessor - force { get; init; } or { get; set; } on all generated properties for immutable / mutable variants

Advanced Features

  • Multi-source mapping — a single target class can carry multiple [Facet] attributes, each mapping from a different source type; produces per-source constructors, projections (ProjectionFrom{Source}), and reverse-mapping methods (To{Source}())
  • [Flatten] - collapse nested object graphs into top-level properties
  • [Wrapper] - reference-based delegation for facades, ViewModels, and decorators
  • [GenerateDtos] - auto-generate full CRUD DTO sets (Create, Update, Response, Query, Upsert, Patch)
  • Source signature tracking - compile-time warning when a source entity's structure changes
  • Inheritance - traverses base classes; suppresses duplicates when facets inherit base types
  • Expression transformation - remap predicates and selectors from entity types to their projections

Integration

  • Full Entity Framework Core support — automatic navigation loading, no .Include() required
  • Works with any LINQ provider via Facet.Extensions
  • Async EF Core variants with cancellation token support
  • Custom async mappers with dependency injection for mappings that require I/O
  • Supports .NET 8, .NET 9, and .NET 10
  • Zero runtime cost, no reflection, everything generated at compile time

:rocket: Quick Start

Installation

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

For advanced EF Core custom mappers (with DI support):

dotnet add package Facet.Extensions.EFCore.Mapping

For expression transformation utilities:

dotnet add package Facet.Mapping.Expressions
Define Facets
…

Create focused facets for different scenarios:

…

Basic Projection of Facets

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

// Map your source to facet
var userFacet = user.ToFacet();
var userFacet = user.ToFacet(); //Much faster

// Map back to source
var user = userFacet.ToSource();
var user = userFacet.ToSource(); //Much faster

// Patch only changed properties back to source
user.ApplyFacet(userFacet);
user.ApplyFacet(userFacet); // Much faster

// Patch with change tracking
bool hasChanges = userFacet.ApplyFacetWithChanges(userFacet);

// LINQ queries
var users = users.SelectFacets();
var users = users.SelectFacets(); //Much faster

Custom Sync Mapping

public class UserMapper : IFacetMapConfiguration
{
    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; }
}

Custom Reverse Mapping with ToSourceConfiguration

When GenerateToSource = true, Facet generates a ToSource() method that maps the DTO back to the source entity. Use ToSourceConfiguration to hook in custom logic for properties that need special treatment on the reverse path, for example, serialising a parsed object back to a JSON column.

…

The Map method in ToSourceConfiguration is called after all auto-mapped properties are copied, so you only need to handle the properties that require custom logic.

Both configs can live in the same class by implementing both interfaces.

Property Mapping with [MapFrom]

Rename properties declaratively without custom mapping configurations:

…

Controlling Reversibility and Projection Inclusion

…

When to Use MapFrom vs Custom Configuration

Use Case MapFrom Custom Config
Simple property rename :white_check_mark: Best choice Overkill
Multiple renames :white_check_mark: Best choice Overkill
Computed values (expressions) :white_check_mark: Supported Alternative
Async operations :x: :white_check_mark: Required
Complex transformations :x: :white_check_mark: Required

Note: MapFrom and custom configurations can be combined. Auto-generated mappings (including MapFrom) are applied first, then the custom mapper is called.

Conditional Mapping with [MapWhen]

Map properties only when specific conditions are met. Perfect for status-dependent fields, null checks, or role-based data exposure:

…

Multiple Conditions (AND Logic)

[Facet(typeof(Order))]
public partial class SecureOrderDto
{
    // Both conditions must be true
    [MapWhen("IsActive")]
    [MapWhen("Status == OrderStatus.Completed")]
    public DateTime? CompletedAt { get; set; }
}

Supported Conditions

  • Boolean: [MapWhen("IsActive")]
  • Equality: [MapWhen("Status == OrderStatus.Completed")]
  • Inequality: [MapWhen("Status != OrderStatus.Cancelled")]
  • Null checks: [MapWhen("Email != null")]
  • Comparisons: [MapWhen("Age >= 18")]
  • Negation: [MapWhen("!IsDeleted")]

Works with EF Core Projections

var orders = await dbContext.Orders
    .Where(o => o.IsActive)
    .SelectFacet()  // Conditions included in SQL
    .ToListAsync();

Before/After Mapping Hooks

Run custom logic before and/or after the automatic property mapping. Perfect for validation, setting defaults, and computing derived values:

…

Combined Hooks

Use IFacetMapHooksConfiguration for both before and after logic in one class:

public class UserMappingHooks : IFacetMapHooksConfiguration
{
    public static void BeforeMap(User source, UserDto target)
    {
        target.MappedAt = DateTime.UtcNow;
    }
    
    public static void AfterMap(User source, UserDto target)
    {
        target.FullName = $"{target.FirstName} {target.LastName}";
    }
}

[Facet(typeof(User), 
    BeforeMapConfiguration = typeof(UserMappingHooks),
    AfterMapConfiguration = typeof(UserMappingHooks))]
public partial class UserDto { }

Async Hooks with Dependency Injection

public class UserEnrichmentHook : IFacetAfterMapConfigurationAsyncInstance
{
    private readonly IProfileService _profileService;
    
    public UserEnrichmentHook(IProfileService profileService)
    {
        _profileService = profileService;
    }
    
    public async Task AfterMapAsync(User source, UserDto target, CancellationToken ct = default)
    {
        target.ProfileUrl = await _profileService.GetProfileUrlAsync(source.Id, ct);
    }
}

When to Use Each Hook

Hook When Called Use Case
BeforeMap Before properties copied Validation, defaults, timestamps
AfterMap After properties copied Computed values, transformations
Configuration (Map) After mapping Simple computed properties

Execution order: BeforeMap → Property Mapping → Configuration.Map → AfterMap

Enum Conversion with ConvertEnumsTo

Automatically convert all enum properties in the source type to string or int in the generated facet. Perfect for API DTOs, serialization, and frontend consumption:

public enum UserStatus { Active, Inactive, Pending, Suspended }

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public UserStatus Status { get; set; }
    public string Email { get; set; }
}

// Convert enums to strings (for JSON APIs)
[Facet(typeof(User), ConvertEnumsTo = typeof(string), GenerateToSource = true)]
public partial class UserStringDto;

// Convert enums to integers (for compact storage)
[Facet(typeof(User), ConvertEnumsTo = typeof(int), GenerateToSource = true)]
public partial class UserIntDto;

Usage

var user = new User { Id = 1, Name = "John", Status = UserStatus.Active, Email = "[email protected]" };

// String conversion
var stringDto = new UserStringDto(user);
stringDto.Status // "Active" (string)

// Int conversion
var intDto = new UserIntDto(user);
intDto.Status // 0 (int)

// Round-trip back to entity
var entity = stringDto.ToSource();
entity.Status // UserStatus.Active (enum)

// Works with LINQ / EF Core projections
var dtos = await dbContext.Users
    .Select(UserStringDto.Projection)
    .ToListAsync();

Nullable Enum Support

Nullable enum properties preserve nullability:

public class Entity
{
    public UserStatus? Status { get; set; }  // Nullable enum
}

[Facet(typeof(Entity), ConvertEnumsTo = typeof(string))]
public partial class EntityDto;
// Status becomes string (null when source is null)

[Facet(typeof(Entity), ConvertEnumsTo = typeof(int))]
public partial class EntityIntDto;
// Status becomes int? (nullable)

Combining with NullableProperties

[Facet(typeof(User), ConvertEnumsTo = typeof(string), NullableProperties = true)]
public partial class UserQueryDto;
// All properties nullable + enums converted to string

Copy Constructor and Value Equality

Generate a copy constructor for clon

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

C#facetgeneratormappermapping

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言