One part of an object, situation, subject that has many parts.

Ever find yourself drowning in hand-written DTOs, mapping methods, and endless AutoMapper profiles? Facetting brings compile-time code generation: define lightweight projections (DTOs, API models, ViewModels) directly from your domain models — no boilerplate, zero runtime cost.

Facet on GitHub & Facet on NuGet

Facet is a C# source generator that emits partial classes, records, structs or record structs with:

  • Public properties/fields matching your source type
  • Strongly-typed constructors
  • LINQ-ready projection expressions
  • Pluggable custom mappings
  • EF Core integration through Facet.Extensions
    … all at compile time, with zero runtime penalty.

What is Facetting?

Facetting is the process of carving out focused views of a richer model at compile time. Instead of manually writing DTOs, mapper classes, and projection lambdas, you declare which members you care about — and the generator writes the rest.

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

Imagine your domain model as a diamond: Facet chisels out exactly the face you need, leaving the rest intact.

Why Facetting?

  • Reduce boilerplate
    Eliminate repeated DTO/mapping code across your solution.
  • Zero runtime overhead
    All mapping logic is generated at compile time — no reflection or IL emit at runtime.
  • Strongly Typed & Nullable-Aware
    Full C# compile-time safety, including nullability contracts.
  • DRY & Performant
    Stay DRY without sacrificing LINQ/EF Core efficiency

Quick start

1. Install the package

dotnet add package Facet

2. Define a Facet

using Facet;

public class Person
{
    public string Name { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
}

// Generate a DTO that excludes Email
[Facet(typeof(Person), exclude: nameof(Person.Email))]
public partial class PersonDto;

This generates:

  • A PersonDto(Person source) constructor
  • Public properties for Name and Age (but not Email)
  • A static Expression<Func<Person,PersonDto>> Projection
  1. Use your generated Facets
var person = new Person { Name = "Alice", Email = "alice@example.com", Age = 30 };

// Constructor-based mapping
var dto = new PersonDto(person);

// LINQ projection
var dtos = dbContext.People
    .Select(PersonDto.Projection)
    .ToList();

Advanced scenarios

Record-style Facets

[Facet(typeof(Person), Kind = FacetKind.Record)]
public partial record PersonRecord;

Struct & Record-Struct Facets

Switch to record semantics:

[Facet(typeof(MyStruct), Kind = FacetKind.Struct, IncludeFields = true)]
public partial struct MyStructDto;

[Facet(typeof(MyRecordStruct), Kind = FacetKind.RecordStruct)]
public partial record struct MyRecordStructDto;

Custom Mapping Logic

Implement IFacetMapConfiguration<TSource,TTarget> to add derived or formatted fields:

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

[Facet(typeof(User), Configuration = typeof(UserMapConfig))]
public partial class UserDto;

Facet will invoke your Map(...) after the default constructor copy, letting you add derived properties or formatting.

LINQ/EF Core integration

Install Facet.Extensions:

dotnet add package Facet.Extensions
using Facet.Extensions;

// Single-object mapping
var dto = person.ToFacet<Person, PersonDto>();

// Enumerable mapping (in-memory)
var dtos = people.SelectFacets<Person, PersonDto>().ToList();

// IQueryable projection (deferred)
var query = dbContext.People.SelectFacet<Person, PersonDto>();
var list  = query.ToList();

// Async with EF Core 6+
var dtosAsync = await dbContext.People
    .SelectFacet<Person, PersonDto>()
    .ToFacetsAsync(cancellationToken);

var firstDto = await dbContext.People
    .FirstFacetAsync<Person, PersonDto>(cancellationToken);

Facet lets you declare once and use everywhere, with zero runtime overhead. Check out the GitHub repo, feel free to contribute, open an issue, or give it a star!


<
Previous Post
Source Generators: The end of T4 templates?
>
Blog Archive
Archive of all previous blog posts