{"id":28581440,"url":"https://github.com/tim-maes/facet","last_synced_at":"2026-05-03T14:05:02.097Z","repository":{"id":289417225,"uuid":"970951585","full_name":"Tim-Maes/Facet","owner":"Tim-Maes","description":"Generate DTOs, mappings, constructors and LINQ projections from your domain models.","archived":false,"fork":false,"pushed_at":"2026-04-10T10:06:54.000Z","size":5380,"stargazers_count":1114,"open_issues_count":15,"forks_count":51,"subscribers_count":9,"default_branch":"master","last_synced_at":"2026-04-10T12:12:44.653Z","etag":null,"topics":["facet","generator","mapper","mapping","model","object","source"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Tim-Maes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["Tim-Maes"],"custom":["https://www.paypal.me/TimMaes91","https://www.buymeacoffee.com/GRq3xSA"]}},"created_at":"2025-04-22T19:31:43.000Z","updated_at":"2026-04-10T10:06:58.000Z","dependencies_parsed_at":"2025-06-03T16:13:50.393Z","dependency_job_id":"1513c477-110c-475a-a281-df2611e368db","html_url":"https://github.com/Tim-Maes/Facet","commit_stats":null,"previous_names":["tim-maes/facet"],"tags_count":138,"template":false,"template_full_name":null,"purl":"pkg:github/Tim-Maes/Facet","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tim-Maes%2FFacet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tim-Maes%2FFacet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tim-Maes%2FFacet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tim-Maes%2FFacet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Tim-Maes","download_url":"https://codeload.github.com/Tim-Maes/Facet/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Tim-Maes%2FFacet/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31691503,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-11T13:07:20.380Z","status":"ssl_error","status_checked_at":"2026-04-11T13:06:47.903Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["facet","generator","mapper","mapping","model","object","source"],"created_at":"2025-06-11T04:17:33.423Z","updated_at":"2026-05-03T14:05:02.068Z","avatar_url":"https://github.com/Tim-Maes.png","language":"C#","funding_links":["https://github.com/sponsors/Tim-Maes","https://www.paypal.me/TimMaes91","https://www.buymeacoffee.com/GRq3xSA"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg\n    src=\"https://raw.githubusercontent.com/Tim-Maes/Facet/master/assets/Facet.png\"\n    alt=\"Facet logo\"\n    width=\"400\"\u003e\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n\"One part of a subject, situation, object that has many parts.\"\n\u003c/div\u003e\n\n\u003cbr\u003e\n\n\u003cdiv align=\"center\"\u003e\n  \n[![CI](https://github.com/Tim-Maes/Facet/actions/workflows/build.yml/badge.svg)](https://github.com/Tim-Maes/Facet/actions/workflows/build.yml)\n[![Test](https://github.com/Tim-Maes/Facet/actions/workflows/test.yml/badge.svg)](https://github.com/Tim-Maes/Facet/actions/workflows/test.yml)\n[![CD](https://github.com/Tim-Maes/Facet/actions/workflows/release.yml/badge.svg)](https://github.com/Tim-Maes/Facet/actions/workflows/release.yml)\n[![NuGet](https://img.shields.io/nuget/v/Facet.svg)](https://www.nuget.org/packages/Facet)\n[![Downloads](https://img.shields.io/nuget/dt/Facet.svg)](https://www.nuget.org/packages/Facet)\n[![GitHub](https://img.shields.io/github/license/Tim-Maes/Facet.svg)](https://github.com/Tim-Maes/Facet/blob/main/LICENSE.txt)\n[![Discord](https://img.shields.io/discord/1443287393825329223?color=%237289da\u0026label=Discord\u0026logo=discord\u0026logoColor=%237289da\u0026style=flat-square)](https://discord.gg/yGDBhGuNMB)\n\n\u003c/div\u003e\n\n---\n\n**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.\n\n## :gem: What is a Facet?\n\nThink of your domain model as a **gem with many facets**! Different views for different purposes:\n- Public APIs need a facet without sensitive data\n- Admin endpoints need a different facet with additional fields\n- Database queries need efficient projections\n\nInstead of manually creating each facet, **Facet** auto-generates them from a single source of truth. Generates constructors, projections, reverse mappings, patch, etc...\n\n## :clipboard: Documentation\n\n- **[Documentation \u0026 Guides](https://github.com/Tim-Maes/Facet/wiki)**\n- **[Facet Dashboard](https://github.com/Tim-Maes/Facet/tree/master/src/Facet.Dashboard)**\n- [What is being generated?](docs/07_WhatIsBeingGenerated.md)\n- [Configure generated files output location](docs/12_GeneratedFilesOutput.md)\n- [Global configuration defaults](docs/21_GlobalConfigurationDefaults.md) - Override attribute defaults project-wide\n- [Comprehensive article about Facetting](https://tim-maes.com/blog/2025/09/28/facets-in-dotnet-(2)/)\n\n## :star: Features\n\n### Code Generation\n- Generate DTOs as classes, records, structs, or record structs\n- Constructor, static factory, and LINQ projection expression, all generated\n- Nested objects and collections mapped automatically\n- Preserves XML documentation and data validation attributes\n\n### Mapping \u0026 Customization\n- Sync and async custom mapping configurations (static or DI-resolved instances)\n- Custom reverse mapping config to map back to source\n- Include/exclude properties with simple attribute arguments\n- **Global Configuration** - override default attribute settings project-wide via MSBuild properties ([docs](docs/21_GlobalConfigurationDefaults.md))\n- **`[MapFrom]`** - declarative property renaming with optional reverse mapping and expression support\n- **`[MapWhen]`** - conditional mapping based on runtime values, works in SQL projections\n- **Before/After hooks** - inject validation, defaults, or computed values around auto-mapping\n- **`ConvertEnumsTo`** - convert all enums to `string` or `int` with full round-trip support\n- **`CollectionTargetType`** - remap source collection types (e.g. `Collection\u003cT\u003e`) to `List\u003cT\u003e` or any target collection type globally per facet; per-property via `[MapFrom(..., AsCollection = typeof(List\u003c\u003e))]`\n- **`GenerateCopyConstructor`** - generate a copy constructor for cloning and MVVM scenarios\n- **`GenerateEquality`** - generate value-based `Equals`, `GetHashCode`, `==`, `!=` for class DTOs\n\n### Advanced Features\n- **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}()`)\n- **`[Flatten]`** - collapse nested object graphs into top-level properties\n- **`[Wrapper]`** - reference-based delegation for facades, ViewModels, and decorators\n- **`[GenerateDtos]`** - auto-generate full CRUD DTO sets (Create, Update, Response, Query, Upsert, Patch)\n- **Source signature tracking** - compile-time warning when a source entity's structure changes\n- **Inheritance** - traverses base classes; suppresses duplicates when facets inherit base types\n- **Expression transformation** - remap predicates and selectors from entity types to their projections\n\n### Integration\n- Full **Entity Framework Core** support — automatic navigation loading, no `.Include()` required\n- Works with any LINQ provider via `Facet.Extensions`\n- Async EF Core variants with cancellation token support\n- Custom async mappers with dependency injection for mappings that require I/O\n- Supports **.NET 8, .NET 9, and .NET 10**\n- Zero runtime cost, no reflection, everything generated at compile time\n\n## :rocket: Quick Start\n\n\u003cdetails\u003e\n  \u003csummary\u003eInstallation\u003c/summary\u003e\n  \n### Install the NuGet Package\n\n```\ndotnet add package Facet\n```\n\nFor LINQ helpers:\n```\ndotnet add package Facet.Extensions\n```\n\nFor EF Core support:\n```\ndotnet add package Facet.Extensions.EFCore\n```\n\nFor advanced EF Core custom mappers (with DI support):\n```\ndotnet add package Facet.Extensions.EFCore.Mapping\n```\n\nFor expression transformation utilities:\n```\ndotnet add package Facet.Mapping.Expressions\n```\n  \n\u003c/details\u003e\n \u003cdetails\u003e\n    \u003csummary\u003eDefine Facets\u003c/summary\u003e\n   \n\n  ```csharp\n // Example domain models:\n\n  public class User\n  {\n      public int Id { get; set; }\n      public string FirstName { get; set; }\n      public string LastName { get; set; }\n      public string Email { get; set; }\n      public string PasswordHash { get; set; }\n      public DateTime DateOfBirth { get; set; }\n      public decimal Salary { get; set; }\n      public string Department { get; set; }\n      public bool IsActive { get; set; }\n      public Address HomeAddress { get; set; }\n      public Company Employer { get; set; }\n      public List\u003cProject\u003e Projects { get; set; }\n      public DateTime CreatedAt { get; set; }\n      public string InternalNotes { get; set; }\n  }\n\n  public class Address\n  {\n      public string Street { get; set; }\n      public string City { get; set; }\n      public string State { get; set; }\n      public string ZipCode { get; set; }\n  }\n\n  public class Company\n  {\n      public int Id { get; set; }\n      public string Name { get; set; }\n      public Address Headquarters { get; set; }\n  }\n\n  public class Project\n  {\n      public int Id { get; set; }\n      public string Name { get; set; }\n      public DateTime StartDate { get; set; }\n  }\n```\n\nCreate focused facets for different scenarios:\n\n```csharp\n  // 1. Public API - Exclude all sensitive data\n  [Facet(typeof(User),\n      exclude: [nameof(User.PasswordHash), nameof(User.Salary), nameof(User.InternalNotes)])]\n  public partial record UserPublicDto;\n\n  // 2. Contact Information - Include only specific properties\n  [Facet(typeof(User),\n      Include = [nameof(User.FirstName), nameof(User.LastName), nameof(User.Email), nameof(User.Department)])]\n  public partial record UserContactDto;\n\n  // 3. Query/Filter DTO - Make all properties nullable\n  [Facet(typeof(User),\n      Include = [nameof(User.FirstName), nameof(User.LastName), nameof(User.Email), nameof(User.Department), nameof(User.IsActive)],\n      NullableProperties = true,\n      GenerateToSource = false)]\n  public partial record UserFilterDto;\n\n  // 4. Validation-Aware DTO - Copy data annotations\n  [Facet(typeof(User),\n      Include = [nameof(User.FirstName), nameof(User.LastName), nameof(User.Email)],\n      CopyAttributes = true)]\n  public partial record UserRegistrationDto;\n\n  // 5. Nested Objects - Single nested facet\n  [Facet(typeof(Address))]\n  public partial record AddressDto;\n\n  [Facet(typeof(User),\n      Include = [nameof(User.Id), nameof(User.FirstName), nameof(User.LastName), nameof(User.HomeAddress)],\n      NestedFacets = [typeof(AddressDto)])]\n  public partial record UserWithAddressDto;\n  // Address -\u003e AddressDto automatically\n  // Type-safe nested mapping\n\n  // 6. Complex Nested - Multiple nested facets\n  [Facet(typeof(Company), NestedFacets = [typeof(AddressDto)])]\n  public partial record CompanyDto;\n\n  [Facet(typeof(User),\n      exclude: [nameof(User.PasswordHash), nameof(User.Salary), nameof(User.InternalNotes)],\n      NestedFacets = [typeof(AddressDto), typeof(CompanyDto)])]\n  public partial record UserDetailDto;\n  // Multi-level nesting supported\n\n  // 7. Collections - Automatic collection mapping, with type override support\n  [Facet(typeof(Project))]\n  public partial record ProjectDto;\n\n  [Facet(typeof(User),\n      Include = [nameof(User.Id), nameof(User.FirstName), nameof(User.LastName), nameof(User.Projects)],\n      NestedFacets = [typeof(ProjectDto)])]\n  public partial record UserWithProjectsDto;\n  // List\u003cProject\u003e -\u003e List\u003cProjectDto\u003e automatically!\n  // All standard collections supported: List, Array, ICollection, IEnumerable, IReadOnlyList, IReadOnlyCollection\n  // System.Collections.Immutable support: ImmutableList, ImmutableArray, ImmutableHashSet, etc.\n  // Custom collections work automatically via interface detection\n\n  // 7b. Collection type override - remap Collection\u003cT\u003e (EF Core) to List\u003cT\u003e in DTOs\n  [Facet(typeof(User),\n      NestedFacets = [typeof(ProjectDto)],\n      CollectionTargetType = typeof(List\u003c\u003e))]\n  public partial record UserListDto;\n  // Collection\u003cProject\u003e on entity -\u003e List\u003cProjectDto\u003e in DTO\n  // ToSource() restores the original Collection\u003cT\u003e automatically\n\n  // 8. Everything Combined\n  [Facet(typeof(User),\n      exclude: [nameof(User.PasswordHash), nameof(User.Salary), nameof(User.InternalNotes)],\n      NestedFacets = [typeof(AddressDto), typeof(CompanyDto), typeof(ProjectDto)],\n      CopyAttributes = true)]\n  public partial record UserCompleteDto;\n  // Excludes sensitive fields\n  // Maps nested Address and Company objects\n  // Maps Projects collection (List\u003cProject\u003e -\u003e List\u003cProjectDto\u003e)\n  // Copies validation attributes\n  // Ready for production APIs\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eBasic Projection of Facets\u003c/summary\u003e\n\n```csharp\n[Facet(typeof(User))]\npublic partial class UserFacet { }\n\n// Map your source to facet\nvar userFacet = user.ToFacet\u003cUserFacet\u003e();\nvar userFacet = user.ToFacet\u003cUser, UserFacet\u003e(); //Much faster\n\n// Map back to source\nvar user = userFacet.ToSource\u003cUser\u003e();\nvar user = userFacet.ToSource\u003cUserFacet, User\u003e(); //Much faster\n\n// Patch only changed properties back to source\nuser.ApplyFacet(userFacet);\nuser.ApplyFacet\u003cUser, UserFacet\u003e(userFacet); // Much faster\n\n// Patch with change tracking\nbool hasChanges = userFacet.ApplyFacetWithChanges\u003cuser, userDto\u003e(userFacet);\n\n// LINQ queries\nvar users = users.SelectFacets\u003cUserFacet\u003e();\nvar users = users.SelectFacets\u003cUser, UserFacet\u003e(); //Much faster\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eCustom Sync Mapping\u003c/summary\u003e\n  \n```csharp\npublic class UserMapper : IFacetMapConfiguration\u003cUser, UserDto\u003e\n{\n    public static void Map(User source, UserDto target)\n    {\n        target.FullName = $\"{source.FirstName} {source.LastName}\";\n        target.Age = CalculateAge(source.DateOfBirth);\n    }\n}\n\n[Facet(typeof(User), Configuration = typeof(UserMapper))]\npublic partial class UserDto \n{\n    public string FullName { get; set; }\n    public int Age { get; set; }\n}\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eCustom Reverse Mapping with ToSourceConfiguration\u003c/summary\u003e\n\nWhen `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.\n\n```csharp\n// Entity with a JSON-stored column\npublic class UnitEntity\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n    public string PrinterSettingsJson { get; set; }  // Stored as JSON in DB\n}\n\n// Forward mapper: Entity \u003e DTO (parse the JSON)\npublic class UnitDtoForwardConfig : IFacetMapConfiguration\u003cUnitEntity, UnitDto\u003e\n{\n    public static void Map(UnitEntity source, UnitDto target)\n        =\u003e target.PrinterSettings = PrinterSettings.Parse(source.PrinterSettingsJson);\n}\n\n// Reverse mapper: DTO \u003e Entity (serialise back to JSON)\npublic class UnitDtoToSourceConfig : IFacetToSourceConfiguration\u003cUnitDto, UnitEntity\u003e\n{\n    public static void Map(UnitDto facet, UnitEntity target)\n        =\u003e target.PrinterSettingsJson = facet.PrinterSettings?.ToJson() ?? \"{}\";\n}\n\n[Facet(typeof(UnitEntity),\n    nameof(UnitEntity.PrinterSettingsJson),          // exclude raw JSON, DTO uses parsed object\n    Configuration = typeof(UnitDtoForwardConfig),\n    ToSourceConfiguration = typeof(UnitDtoToSourceConfig),\n    GenerateToSource = true)]\npublic partial class UnitDto\n{\n    public PrinterSettings? PrinterSettings { get; set; }\n}\n\n// Usage\nvar dto = new UnitDto(entity);           // PrinterSettingsJson \u003e PrinterSettings (parsed)\nvar entity = dto.ToSource();             // PrinterSettings \u003e PrinterSettingsJson (serialised)\n```\n\nThe `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.\n\n\u003e Both configs can live in the same class by implementing both interfaces.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eProperty Mapping with [MapFrom]\u003c/summary\u003e\n\nRename properties declaratively without custom mapping configurations:\n\n```csharp\npublic class User\n{\n    public int Id { get; set; }\n    public string FirstName { get; set; }\n    public string LastName { get; set; }\n    public string Email { get; set; }\n}\n\n[Facet(typeof(User), GenerateToSource = true)]\npublic partial class UserDto\n{\n    // Type-safe property rename with reverse mapping\n    [MapFrom(nameof(User.FirstName), Reversible = true)]\n    public string Name { get; set; } = string.Empty;\n\n    // Rename multiple properties\n    [MapFrom(nameof(User.LastName), Reversible = true)]\n    public string FamilyName { get; set; } = string.Empty;\n\n    // Computed expression (not reversible)\n    [MapFrom(\"FirstName + \\\" \\\" + LastName\")]\n    public string FullName { get; set; } = string.Empty;\n}\n\n// Usage\nvar user = new User { Id = 1, FirstName = \"John\", LastName = \"Doe\", Email = \"john@example.com\" };\nvar dto = new UserDto(user);\n// dto.Name = \"John\" (mapped from FirstName)\n// dto.FamilyName = \"Doe\" (mapped from LastName)\n// dto.FullName = \"John Doe\" (computed expression)\n\n// Reverse mapping works automatically\nvar entity = dto.ToSource();\n// entity.FirstName = \"John\" (mapped from Name)\n// entity.LastName = \"Doe\" (mapped from FamilyName)\n\n// Projections also work\nvar dtos = users.SelectFacet\u003cUserDto\u003e().ToList();\n```\n\n#### Controlling Reversibility and Projection Inclusion\n\n```csharp\n[Facet(typeof(User), GenerateToSource = true)]\npublic partial class UserDto\n{\n    // Reversible mapping (included in ToSource) - opt-in\n    [MapFrom(nameof(User.FirstName), Reversible = true)]\n    public string Name { get; set; } = string.Empty;\n\n    // Default: not reversible (one-way, source → DTO only)\n    [MapFrom(nameof(User.LastName))]\n    public string DisplayName { get; set; } = string.Empty;\n\n    // Exclude from EF Core projection (for client-side computed values)\n    [MapFrom(\"Name.ToUpper()\", IncludeInProjection = false)]\n    public string UpperName { get; set; } = string.Empty;\n}\n```\n\n#### When to Use MapFrom vs Custom Configuration\n\n| Use Case | MapFrom | Custom Config |\n|----------|---------|---------------|\n| Simple property rename | :white_check_mark: Best choice | Overkill |\n| Multiple renames | :white_check_mark: Best choice | Overkill |\n| Computed values (expressions) | :white_check_mark: Supported | Alternative |\n| Async operations | :x: | :white_check_mark: Required |\n| Complex transformations | :x: | :white_check_mark: Required |\n\n**Note**: MapFrom and custom configurations can be combined. Auto-generated mappings (including MapFrom) are applied first, then the custom mapper is called.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eConditional Mapping with [MapWhen]\u003c/summary\u003e\n\nMap properties only when specific conditions are met. Perfect for status-dependent fields, null checks, or role-based data exposure:\n\n```csharp\npublic class Order\n{\n    public int Id { get; set; }\n    public OrderStatus Status { get; set; }\n    public DateTime? CompletedAt { get; set; }\n    public string? TrackingNumber { get; set; }\n    public bool IsActive { get; set; }\n    public string? Email { get; set; }\n}\n\n[Facet(typeof(Order))]\npublic partial class OrderDto\n{\n    // Only map when status is Completed\n    [MapWhen(\"Status == OrderStatus.Completed\")]\n    public DateTime? CompletedAt { get; set; }\n\n    // Only map when not cancelled\n    [MapWhen(\"Status != OrderStatus.Cancelled\")]\n    public string? TrackingNumber { get; set; }\n\n    // Boolean condition\n    [MapWhen(\"IsActive\")]\n    public string? Email { get; set; }\n}\n\n// Usage\nvar order = new Order\n{\n    Id = 1,\n    Status = OrderStatus.Completed,\n    CompletedAt = DateTime.Now,\n    IsActive = true,\n    Email = \"user@example.com\"\n};\n\nvar dto = new OrderDto(order);\n// dto.CompletedAt = DateTime.Now (condition true)\n// dto.Email = \"user@example.com\" (IsActive is true)\n\nvar pendingOrder = new Order { Status = OrderStatus.Pending, Email = \"test@example.com\" };\nvar pendingDto = new OrderDto(pendingOrder);\n// pendingDto.CompletedAt = null (condition false)\n```\n\n#### Multiple Conditions (AND Logic)\n\n```csharp\n[Facet(typeof(Order))]\npublic partial class SecureOrderDto\n{\n    // Both conditions must be true\n    [MapWhen(\"IsActive\")]\n    [MapWhen(\"Status == OrderStatus.Completed\")]\n    public DateTime? CompletedAt { get; set; }\n}\n```\n\n#### Supported Conditions\n\n- **Boolean**: `[MapWhen(\"IsActive\")]`\n- **Equality**: `[MapWhen(\"Status == OrderStatus.Completed\")]`\n- **Inequality**: `[MapWhen(\"Status != OrderStatus.Cancelled\")]`\n- **Null checks**: `[MapWhen(\"Email != null\")]`\n- **Comparisons**: `[MapWhen(\"Age \u003e= 18\")]`\n- **Negation**: `[MapWhen(\"!IsDeleted\")]`\n\n#### Works with EF Core Projections\n\n```csharp\nvar orders = await dbContext.Orders\n    .Where(o =\u003e o.IsActive)\n    .SelectFacet\u003cOrderDto\u003e()  // Conditions included in SQL\n    .ToListAsync();\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eBefore/After Mapping Hooks\u003c/summary\u003e\n\nRun custom logic before and/or after the automatic property mapping. Perfect for validation, setting defaults, and computing derived values:\n\n```csharp\nusing Facet.Mapping;\n\n// BeforeMap - runs BEFORE properties are copied\npublic class UserBeforeMapConfig : IFacetBeforeMapConfiguration\u003cUser, UserDto\u003e\n{\n    public static void BeforeMap(User source, UserDto target)\n    {\n        // Validate input\n        if (string.IsNullOrEmpty(source.Email))\n            throw new ValidationException(\"Email is required\");\n        \n        // Set defaults on target\n        target.MappedAt = DateTime.UtcNow;\n    }\n}\n\n// AfterMap - runs AFTER properties are copied\npublic class UserAfterMapConfig : IFacetAfterMapConfiguration\u003cUser, UserDto\u003e\n{\n    public static void AfterMap(User source, UserDto target)\n    {\n        // Compute derived values\n        target.FullName = $\"{target.FirstName} {target.LastName}\";\n        target.Age = CalculateAge(source.DateOfBirth);\n    }\n}\n\n// Apply hooks via attribute\n[Facet(typeof(User), \n    BeforeMapConfiguration = typeof(UserBeforeMapConfig),\n    AfterMapConfiguration = typeof(UserAfterMapConfig))]\npublic partial class UserDto\n{\n    public DateTime MappedAt { get; set; }\n    public string FullName { get; set; } = string.Empty;\n    public int Age { get; set; }\n}\n```\n\n#### Combined Hooks\n\nUse `IFacetMapHooksConfiguration` for both before and after logic in one class:\n\n```csharp\npublic class UserMappingHooks : IFacetMapHooksConfiguration\u003cUser, UserDto\u003e\n{\n    public static void BeforeMap(User source, UserDto target)\n    {\n        target.MappedAt = DateTime.UtcNow;\n    }\n    \n    public static void AfterMap(User source, UserDto target)\n    {\n        target.FullName = $\"{target.FirstName} {target.LastName}\";\n    }\n}\n\n[Facet(typeof(User), \n    BeforeMapConfiguration = typeof(UserMappingHooks),\n    AfterMapConfiguration = typeof(UserMappingHooks))]\npublic partial class UserDto { }\n```\n\n#### Async Hooks with Dependency Injection\n\n```csharp\npublic class UserEnrichmentHook : IFacetAfterMapConfigurationAsyncInstance\u003cUser, UserDto\u003e\n{\n    private readonly IProfileService _profileService;\n    \n    public UserEnrichmentHook(IProfileService profileService)\n    {\n        _profileService = profileService;\n    }\n    \n    public async Task AfterMapAsync(User source, UserDto target, CancellationToken ct = default)\n    {\n        target.ProfileUrl = await _profileService.GetProfileUrlAsync(source.Id, ct);\n    }\n}\n```\n\n#### When to Use Each Hook\n\n| Hook | When Called | Use Case |\n|------|-------------|----------|\n| BeforeMap | Before properties copied | Validation, defaults, timestamps |\n| AfterMap | After properties copied | Computed values, transformations |\n| Configuration (Map) | After mapping | Simple computed properties |\n\n**Execution order**: BeforeMap → Property Mapping → Configuration.Map → AfterMap\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eEnum Conversion with ConvertEnumsTo\u003c/summary\u003e\n\nAutomatically convert all enum properties in the source type to `string` or `int` in the generated facet. Perfect for API DTOs, serialization, and frontend consumption:\n\n```csharp\npublic enum UserStatus { Active, Inactive, Pending, Suspended }\n\npublic class User\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n    public UserStatus Status { get; set; }\n    public string Email { get; set; }\n}\n\n// Convert enums to strings (for JSON APIs)\n[Facet(typeof(User), ConvertEnumsTo = typeof(string), GenerateToSource = true)]\npublic partial class UserStringDto;\n\n// Convert enums to integers (for compact storage)\n[Facet(typeof(User), ConvertEnumsTo = typeof(int), GenerateToSource = true)]\npublic partial class UserIntDto;\n```\n\n#### Usage\n\n```csharp\nvar user = new User { Id = 1, Name = \"John\", Status = UserStatus.Active, Email = \"john@test.com\" };\n\n// String conversion\nvar stringDto = new UserStringDto(user);\nstringDto.Status // \"Active\" (string)\n\n// Int conversion\nvar intDto = new UserIntDto(user);\nintDto.Status // 0 (int)\n\n// Round-trip back to entity\nvar entity = stringDto.ToSource();\nentity.Status // UserStatus.Active (enum)\n\n// Works with LINQ / EF Core projections\nvar dtos = await dbContext.Users\n    .Select(UserStringDto.Projection)\n    .ToListAsync();\n```\n\n#### Nullable Enum Support\n\nNullable enum properties preserve nullability:\n\n```csharp\npublic class Entity\n{\n    public UserStatus? Status { get; set; }  // Nullable enum\n}\n\n[Facet(typeof(Entity), ConvertEnumsTo = typeof(string))]\npublic partial class EntityDto;\n// Status becomes string (null when source is null)\n\n[Facet(typeof(Entity), ConvertEnumsTo = typeof(int))]\npublic partial class EntityIntDto;\n// Status becomes int? (nullable)\n```\n\n#### Combining with NullableProperties\n\n```csharp\n[Facet(typeof(User), ConvertEnumsTo = typeof(string), NullableProperties = true)]\npublic partial class UserQueryDto;\n// All properties nullable + enums converted to string\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eCopy Constructor and Value Equality\u003c/summary\u003e\n\nGenerate a copy constructor for cloning DTOs, and/or value-based equality members for class-based facets:\n\n#### Copy Constructor\n\n```csharp\n[Facet(typeof(User), GenerateCopyConstructor = true)]\npublic partial class UserDto;\n\n// Clone a DTO\nvar original = new UserDto(user);\nvar copy = new UserDto(original); // Copy constructor\n\n// Modify the copy without affecting the original\ncopy.FirstName = \"Changed\";\noriginal.FirstName; // Still \"John\"\n```\n\nUse cases: MVVM undo/redo, caching snapshots, editing copies while preserving originals.\n\n#### Value Equality\n\n```csharp\n[Facet(typeof(User), GenerateEquality = true)]\npublic partial class UserDto;\n\nvar dto1 = new UserDto(user);\nvar dto2 = new UserDto(user);\n\ndto1 == dto2;          // true — value-based comparison\ndto1.Equals(dto2);     // true\ndto1.GetHashCode() == dto2.GetHashCode(); // true\n\n// Works in collections\nvar set = new HashSet\u003cUserDto\u003e { dto1 };\nset.Contains(dto2);    // true\n```\n\nThe generated type implements `IEquatable\u003cT\u003e` with `Equals(T)`, `Equals(object)`, `GetHashCode()`, `==`, and `!=`.\n\n\u003e **Note:** `GenerateEquality` is automatically ignored for record types, which already have built-in value equality.\n\n#### Combining Both\n\n```csharp\n[Facet(typeof(User), GenerateCopyConstructor = true, GenerateEquality = true)]\npublic partial class UserDto;\n\nvar original = new UserDto(user);\nvar copy = new UserDto(original);\noriginal == copy; // true — same values, different instances\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eInheritance Mapping\u003c/summary\u003e\n\nFacet fully supports inheritance hierarchies. Properties from base classes are automatically included:\n\n```csharp\n// Base domain model\npublic class User\n{\n    public int Id { get; set; }\n    public string FirstName { get; set; }\n    public string Email { get; set; }\n    public string Password { get; set; }  // Sensitive\n}\n\n// Derived domain model\npublic class Employee : User\n{\n    public string Department { get; set; }\n    public decimal Salary { get; set; }  // Sensitive\n}\n\n// Facet for Employee - includes User properties automatically\n[Facet(typeof(Employee), \"Password\", \"Salary\")]\npublic partial class EmployeeDto;\n\n// Generated properties:\n// From User: Id, FirstName, Email\n// From Employee: Department\n// Excluded: Password, Salary\n```\n\n#### Facets with Base Classes\n\nYour facet types can also inherit from base classes. Facet won't duplicate inherited properties:\n\n```csharp\n// Shared base facet\npublic abstract class BaseFacet\n{\n    public int Id { get; set; }\n    public bool IsActive { get; set; }\n}\n\n// Facet inherits from base - Id and IsActive come from base\n[Facet(typeof(Product), \"InternalCode\")]\npublic partial class ProductDto : BaseFacet\n{\n    // Generated: Name, Description, Price\n    // Inherited from BaseFacet: Id, IsActive (NOT duplicated)\n}\n```\n\n#### Generic Base Classes\n\n```csharp\npublic class BaseEntity\u003cTKey\u003e\n{\n    public TKey Id { get; set; }\n}\n\npublic class Category : BaseEntity\u003cuint\u003e\n{\n    public string Name { get; set; }\n}\n\n// Exclude Id from generic base\n[Facet(typeof(Category), \"Id\")]\npublic partial class UpdateCategoryDto;\n// Result: Name only (Id excluded)\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eAsync Mapping for I/O Operations\u003c/summary\u003e\n  \n```csharp\npublic class UserAsyncMapper : IFacetMapConfigurationAsync\u003cUser, UserDto\u003e\n{\n    public static async Task MapAsync(User source, UserDto target, CancellationToken cancellationToken = default)\n    {\n        // Async database lookup\n        target.ProfilePicture = await GetProfilePictureAsync(source.Id, cancellationToken);\n        \n        // Async API call\n        target.ReputationScore = await CalculateReputationAsync(source.Email, cancellationToken);\n    }\n}\n\n// Usage\nvar userDto = await user.ToFacetAsync\u003cUser, UserDto, UserAsyncMapper\u003e();\nvar userDtos = await users.ToFacetsParallelAsync\u003cUser, UserDto, UserAsyncMapper\u003e();\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eAsync Mapping with Dependency Injection\u003c/summary\u003e\n  \n```csharp\npublic class UserAsyncMapperWithDI : IFacetMapConfigurationAsyncInstance\u003cUser, UserDto\u003e\n{\n    private readonly IProfilePictureService _profileService;\n    private readonly IReputationService _reputationService;\n\n    public UserAsyncMapperWithDI(IProfilePictureService profileService, IReputationService reputationService)\n    {\n        _profileService = profileService;\n        _reputationService = reputationService;\n    }\n\n    public async Task MapAsync(User source, UserDto target, CancellationToken cancellationToken = default)\n    {\n        // Use injected services\n        target.ProfilePicture = await _profileService.GetProfilePictureAsync(source.Id, cancellationToken);\n        target.ReputationScore = await _reputationService.CalculateReputationAsync(source.Email, cancellationToken);\n    }\n}\n\n// Usage with DI\nvar mapper = new UserAsyncMapperWithDI(profileService, reputationService);\nvar userDto = await user.ToFacetAsync(mapper);\nvar userDtos = await users.ToFacetsParallelAsync(mapper);\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eEF Core Integration\u003c/summary\u003e\n\n  #### Forward Mapping (Entity -\u003e Facet)\n```csharp\n// Async projection directly in EF Core queries\nvar userDtos = await dbContext.Users\n    .Where(u =\u003e u.IsActive)\n    .ToFacetsAsync\u003cUserDto\u003e();\n\n// LINQ projection for complex queries\nvar results = await dbContext.Products\n    .Where(p =\u003e p.IsAvailable)\n    .SelectFacet\u003cProductDto\u003e()\n    .OrderBy(dto =\u003e dto.Name)\n    .ToListAsync();\n```\n\n#### Automatic Navigation Property Loading (No .Include() Required!)\n```csharp\n// Define nested facets\n[Facet(typeof(Address))]\npublic partial record AddressDto;\n\n[Facet(typeof(Company), NestedFacets = [typeof(AddressDto)])]\npublic partial record CompanyDto;\n\n// Navigation properties are automatically loaded - no .Include() needed!\nvar companies = await dbContext.Companies\n    .Where(c =\u003e c.IsActive)\n    .SelectFacet\u003cCompanyDto\u003e()\n    .ToListAsync();\n\n// The HeadquartersAddress navigation property is automatically included!\n// EF Core analyzes the projection expression and generates the necessary JOINs\n\n// This also works with collections:\n[Facet(typeof(OrderItem))]\npublic partial record OrderItemDto;\n\n[Facet(typeof(Order), NestedFacets = [typeof(OrderItemDto), typeof(AddressDto)])]\npublic partial record OrderDto;\n\nvar orders = await dbContext.Orders\n    .SelectFacet\u003cOrderDto\u003e()  // Automatically includes Items collection and ShippingAddress!\n    .ToListAsync();\n```\n\n#### Reverse Mapping (Facet -\u003e Entity)\n```csharp\n[Facet(typeof(User)]\npublic partial class UpdateUserDto { }\n\n[HttpPut(\"{id}\")]\npublic async Task\u003cIActionResult\u003e UpdateUser(int id, UpdateUserDto dto)\n{\n    var user = await context.Users.FindAsync(id);\n    if (user == null) return NotFound();\n\n    // Only updates properties that mutated\n    user.UpdateFromFacet(dto, context);\n\n    await context.SaveChangesAsync();\n    return NoContent();\n}\n\n// With change tracking for auditing\nvar result = user.UpdateFromFacetWithChanges(dto, context);\nif (result.HasChanges)\n{\n    logger.LogInformation(\"User {UserId} updated. Changed: {Properties}\",\n        user.Id, string.Join(\", \", result.ChangedProperties));\n}\n```\n\n#### Advanced: Custom Mappers with EF Core (Facet.Extensions.EFCore.Mapping)\n\nFor complex mappings that cannot be expressed as SQL projections (e.g., external service calls, complex type conversions), use the advanced mapping package:\n\n```csharp\n// Install: dotnet add package Facet.Extensions.EFCore.Mapping\nusing Facet.Extensions.EFCore.Mapping;\n\n// Example: Converting separate X, Y properties into a Vector2 type\n[Facet(typeof(User), exclude: [nameof(User.X), nameof(User.Y)])]\npublic partial class UserDto\n{\n    public Vector2 Position { get; set; }\n}\n\n// Static mapper\npublic class UserMapper : IFacetMapConfigurationAsync\u003cUser, UserDto\u003e\n{\n    public static async Task MapAsync(User source, UserDto target, CancellationToken cancellationToken = default)\n    {\n        target.Position = new Vector2(source.X, source.Y);\n    }\n}\n\n// Usage with EF Core queries\nvar users = await dbContext.Users\n    .Where(u =\u003e u.IsActive)\n    .ToFacetsAsync\u003cUser, UserDto, UserMapper\u003e();\n\n// Or with dependency injection\npublic class UserMapper : IFacetMapConfigurationAsyncInstance\u003cUser, UserDto\u003e\n{\n    private readonly ILocationService _locationService;\n\n    public UserMapper(ILocationService locationService)\n    {\n        _locationService = locationService;\n    }\n\n    public async Task MapAsync(User source, UserDto target, CancellationToken cancellationToken = default)\n    {\n        target.Position = new Vector2(source.X, source.Y);\n        target.Location = await _locationService.GetLocationAsync(source.LocationId);\n    }\n}\n\n// Usage with DI\nvar users = await dbContext.Users\n    .Where(u =\u003e u.IsActive)\n    .ToFacetsAsync\u003cUser, UserDto\u003e(userMapper);\n```\n\n**Note**: Custom mapper methods materialize the query first (execute SQL), then apply your custom logic. All matching properties are auto-mapped first.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eAutomatic CRUD DTO Generation with [GenerateDtos]\u003c/summary\u003e\n  \nGenerate standard Create, Update, Response, Query, Upsert, and Patch DTOs automatically:\n\n```csharp\n// Generate all standard CRUD DTOs\n[GenerateDtos(Types = DtoTypes.All, OutputType = OutputType.Record)]\npublic class User\n{\n    public int Id { get; set; }\n    public string FirstName { get; set; }\n    public string LastName { get; set; }\n    public string Email { get; set; }\n    public DateTime CreatedAt { get; set; }\n}\n\n// Auto-generates:\n// - CreateUserRequest (excludes Id)\n// - UpdateUserRequest (includes Id)  \n// - UserResponse (includes all)\n// - UserQuery (all properties nullable)\n// - UpsertUserRequest (includes Id, for create/update operations)\n```\n\n#### Entities with Smart Exclusions\n\n```csharp\n[GenerateAuditableDtos(\n    Types = DtoTypes.Create | DtoTypes.Update | DtoTypes.Response,\n    OutputType = OutputType.Record,\n    ExcludeProperties = [nameof(Product.Password)])]\npublic class Product\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n    public string Password { get; set; } // Excluded\n    public DateTime CreatedAt { get; set; } // Auto-excluded (audit)\n    public string CreatedBy { get; set; } // Auto-excluded (audit)\n}\n\n// Auto-excludes audit fields: CreatedAt, UpdatedAt, CreatedBy, UpdatedBy\n```\n\n#### Multiple Configurations for Fine-Grained Control\n```csharp\n// Different exclusions for different DTO types\n[GenerateDtos(Types = DtoTypes.Response, ExcludeProperties = [nameof(Schedule.Password), nameof(Schedule.InternalNotes)])]\n[GenerateDtos(Types = DtoTypes.Upsert, ExcludeProperties = [nameof(Schedule.Password)])]\npublic class Schedule\n{\n    public int Id { get; set; }\n    public string Name { get; set; }\n    public string Password { get; set; } // Excluded from both\n    public string InternalNotes { get; set; } // Only excluded from Response\n}\n\n// Generates:\n// - ScheduleResponse (excludes Password, InternalNotes) \n// - UpsertScheduleRequest (excludes Password, includes InternalNotes)\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eFlatten nested objects with [Flatten]\u003c/summary\u003e\n\nFlatten nested object hierarchies into top-level properties automatically - perfect for API responses, reports, and denormalized views:\n\n```csharp\n// Domain models with nested structure\npublic class Person\n{\n    public int Id { get; set; }\n    public string FirstName { get; set; }\n    public string LastName { get; set; }\n    public Address Address { get; set; }\n    public ContactInfo ContactInfo { get; set; }\n}\n\npublic class Address\n{\n    public string Street { get; set; }\n    public string City { get; set; }\n    public string ZipCode { get; set; }\n    public Country Country { get; set; }\n}\n\npublic class Country\n{\n    public string Name { get; set; }\n    public string Code { get; set; }\n}\n\npublic class ContactInfo\n{\n    public string Email { get; set; }\n    public string Phone { get; set; }\n}\n\n// Automatically flatten all nested properties\n[Flatten(typeof(Person))]\npublic partial class PersonFlatDto\n{\n    // Auto-generates:\n    // public int Id { get; set; }\n    // public string FirstName { get; set; }\n    // public string LastName { get; set; }\n    // public string AddressStreet { get; set; }\n    // public string AddressCity { get; set; }\n    // public string AddressZipCode { get; set; }\n    // public string AddressCountryName { get; set; }\n    // public string AddressCountryCode { get; set; }\n    // public string ContactInfoEmail { get; set; }\n    // public string ContactInfoPhone { get; set; }\n}\n\n// Usage with constructor\nvar person = new Person\n{\n    FirstName = \"John\",\n    Address = new Address\n    {\n        Street = \"123 Main St\",\n        City = \"Springfield\",\n        Country = new Country { Name = \"USA\", Code = \"US\" }\n    },\n    ContactInfo = new ContactInfo { Email = \"john@example.com\" }\n};\n\nvar dto = new PersonFlatDto(person);\n// dto.AddressStreet = \"123 Main St\"\n// dto.AddressCountryName = \"USA\"\n\n// Usage with Entity Framework projection\nvar flatDtos = await dbContext.People\n    .Where(p =\u003e p.IsActive)\n    .Select(PersonFlatDto.Projection)\n    .ToListAsync();\n```\n\n#### Controlling Depth and Exclusions\n\n```csharp\n// Limit flattening depth\n[Flatten(typeof(Person), MaxDepth = 2)]\npublic partial class PersonFlatDepth2Dto\n{\n    // Includes Address.Street and Address.City\n    // Does NOT include Address.Country.* (beyond depth 2)\n}\n\n// Exclude specific paths\n[Flatten(typeof(Person), nameof(Person.ContactInfo))]\npublic partial class PersonFlatWithoutContactDto\n{\n    // All properties except ContactInfo.*\n}\n\n[Flatten(typeof(Person), $\"{nameof(Person.Address)}.{nameof(Address.Country)}\")]\npublic partial class PersonFlatWithoutCountryDto\n{\n    // Includes Address.Street, Address.City\n    // Excludes Address.Country.*\n}\n```\n\n#### Naming Strategies\n\n```csharp\n// Prefix strategy (default): AddressStreet, AddressCity\n[Flatten(typeof(Person), NamingStrategy = FlattenNamingStrategy.Prefix)]\npublic partial class PersonFlatPrefixDto { }\n\n// Leaf-only strategy: Street, City (may cause collisions)\n[Flatten(typeof(Person), NamingStrategy = FlattenNamingStrategy.LeafOnly)]\npublic partial class PersonFlatLeafDto { }\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eReference-based Wrappers with [Wrapper]\u003c/summary\u003e\n\nGenerate wrapper classes that **delegate** to a source object instead of copying values. Unlike `[Facet]` which creates independent copies, wrappers maintain a reference to the source, so changes propagate:\n\n```csharp\n// Domain model\npublic class User\n{\n    public int Id { get; set; }\n    public string FirstName { get; set; }\n    public string LastName { get; set; }\n    public string Email { get; set; }\n    public string Password { get; set; }  // Sensitive!\n    public decimal Salary { get; set; }   // Sensitive!\n}\n\n// Hide sensitive properties with a facade\n[Wrapper(typeof(User), nameof(User.Password), nameof(User.Salary))]\npublic partial class PublicUserWrapper { }\n\n// Usage - changes propagate to source!\nvar user = new User { Id = 1, FirstName = \"John\", Password = \"secret\" };\nvar wrapper = new PublicUserWrapper(user);\n\nwrapper.FirstName = \"Jane\";\nConsole.WriteLine(user.FirstName);  // \"Jane\" - source is modified!\n\n// Sensitive properties not accessible\n// wrapper.Password;  // Compile error\n// wrapper.Salary;    // Compile error\n```\n\n#### Read-Only Wrappers (Immutable Facades)\n\n```csharp\n// Prevent modifications with ReadOnly mode\n[Wrapper(typeof(Product), ReadOnly = true)]\npublic partial class ReadOnlyProductView { }\n\nvar product = new Product { Name = \"Laptop\", Price = 1299.99m };\nvar view = new ReadOnlyProductView(product);\n\n// Can read\nConsole.WriteLine(view.Name);\n\n// Cannot write (compile error CS0200)\n// view.Name = \"Desktop\";  // Property is read-only\n\n// Still reflects source changes\nproduct.Name = \"Desktop\";\nConsole.WriteLine(view.Name);  // \"Desktop\"\n```\n\n#### Use Cases\n\n- **Facade Pattern**: Hide sensitive/internal properties from API consumers\n- **ViewModel Pattern**: Expose domain model subset to UI with live binding\n- **Decorator Pattern**: Add behavior without modifying domain models\n- **Memory Efficiency**: Avoid duplicating large object graphs\n- **Read-only Views**: Immutable facades\n\n#### Wrapper vs Facet\n\n| Aspect | Facet (Value Copy) | Wrapper (Reference) |\n|--------|-------------------|---------------------|\n| **Data Storage** | Independent copy | Reference to source |\n| **Memory** | Duplicates data | No duplication |\n| **Changes** | Independent | Synchronized to source |\n| **Use Case** | DTOs, EF projections | Facades, ViewModels |\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eSource Signature Tracking for Breaking Change Detection\u003c/summary\u003e\n\nTrack changes to your source entities and get compile-time warnings when the structure changes. This helps prevent unintended API breaking changes when your EF Core models are modified.\n\n#### Basic Usage\n\n```csharp\n// Without tracking (default behavior)\n[Facet(typeof(User))]\npublic partial class UserDto;\n\n// With change detection - add a SourceSignature\n[Facet(typeof(User), SourceSignature = \"a1b2c3d4\")]\npublic partial class UserDto;\n```\n\n#### How It Works\n\n1. The signature is an 8-character hash based on property names and types\n2. When the source entity changes (properties added/removed/renamed), the hash changes\n3. A compile-time warning (FAC022) alerts you to review and acknowledge the change\n\n#### Example Warning\n\nWhen someone adds a new property to the `User` entity:\n\n```\nwarning FAC022: Source entity 'User' structure has changed.\nUpdate SourceSignature to 'e5f6g7h8' to acknowledge this change.\n```\n\n#### IDE Code Fix\n\nIn Visual Studio or Rider, click the lightbulb to automatically update the signature:\n\n**\"Update SourceSignature to 'e5f6g7h8'\"**\n\n#### Benefits\n\n| Benefit | Description |\n|---------|-------------|\n| **Prevents data exposure** | New sensitive fields don't silently appear in API responses |\n| **Catches breaking changes** | Removed fields are detected before runtime errors |\n| **Explicit acknowledgment** | Forces review of changes before they reach production |\n| **Opt-in** | Only active when you set a SourceSignature |\n\n#### Workflow\n\n1. During development, use Facet without signatures for flexibility\n2. Before release, add `SourceSignature` to lock the API contract\n3. When source entities change, the warning reminds you to review\n4. Update the signature to acknowledge intentional changes\n\n\u003c/details\u003e\n\n## :earth_americas: The Facet Ecosystem\n\nFacet is modular and consists of several NuGet packages:\n\n- **[Facet](https://github.com/Tim-Maes/Facet/blob/master/README.md)**: The core source generator. Generates DTOs, projections, and mapping code.\n- **[Facet.Extensions](https://github.com/Tim-Maes/Facet/blob/master/src/Facet.Extensions/README.md)**: Provider-agnostic extension methods for mapping, projecting and patch updates (works with any LINQ provider, no EF Core dependency).\n- **[Facet.Mapping](https://github.com/Tim-Maes/Facet/tree/master/src/Facet.Mapping)**: Advanced static mapping configuration support with async capabilities and dependency injection for complex mapping scenarios.\n- **[Facet.Mapping.Expressions](https://github.com/Tim-Maes/Facet/blob/master/src/Facet.Mapping.Expressions/README.md)**: Expression tree transformation utilities for transforming predicates, selectors, and business logic between source entities and their Facet projections.\n- **[Facet.Extensions.EFCore](https://github.com/Tim-Maes/Facet/tree/master/src/Facet.Extensions.EFCore)**: Async extension methods for Entity Framework Core (requires EF Core 6+).\n- **[Facet.Extensions.EFCore.Mapping](https://github.com/Tim-Maes/Facet/tree/master/src/Facet.Extensions.EFCore.Mapping)**: Advanced custom async mapper support for EF Core queries. Enables complex mappings that cannot be expressed as SQL projections \n\n## Facet Dashboard\n\nUse `Facet.Dashboard` to visualize your Facets!\n\n\u003cimg width=\"1595\" height=\"1311\" alt=\"image\" src=\"https://github.com/user-attachments/assets/af99d28d-d83b-4807-b7c4-8dad192dc9c4\" /\u003e\n\n\n## Comparison\n\n| Feature | Facet | AutoMapper | Mapperly | Mapster |\n|---------|-------|------------|----------|---------|\n| **Generation Time** | Compile | Runtime | Compile | Runtime |\n| **EF Core Projections** | :white_check_mark: Auto | :x: Manual | :warning: Manual | :warning: Manual |\n| **Navigation Loading** | :white_check_mark: Auto | :x: Manual | :x: Manual | :x: Manual |\n| **Flatten/Wrapper/CRUD** | :white_check_mark: Built-in | :x: | :x: | :warning: Limited |\n| **Expression Transform** | :white_check_mark: | :x: | :x: | :x: |\n| **Breaking Detection** | :white_check_mark: | :x: | :x: | :x: |\n| **Conditional Mapping** | :white_check_mark: MapWhen | :warning: Custom | :warning: Custom | :warning: Custom |\n| **Before/After Hooks** | :white_check_mark: Built-in | :white_check_mark: | :warning: Manual | :warning: Custom |\n\n**Facet is the only tool that combines compile-time generation with deep EF Core integration.**\n\n## Performance Benchmarks\n\nNote that these are perfomed by using the `\u003cTSource, TDestination\u003e` mapping method overloads wherever possible, as they are significantly faster than the `\u003cTDestination\u003e` versions.\n\n**Simple mapping**\n\n| Method           | Mean      | Ratio        | Allocated | Alloc Ratio |\n|----------------- |----------:|-------------:|----------:|------------:|\n| Facet            |  5.922 ns |     baseline |      40 B |             |\n| Mapperly         |  6.227 ns | 1.05x slower |      40 B |  1.00x more |\n| Mapster | 13.243 ns | 2.24x slower |      40 B |  1.00x more |\n| AutoMapper       | 31.459 ns | 5.31x slower |      40 B |  1.00x more |\n\n**Nested mapping**\n\n| Method           | Mean      | Ratio        | Allocated | Alloc Ratio |\n|----------------- |----------:|-------------:|----------:|------------:|\n| Facet            |  5.497 ns |     baseline |      32 B |             |\n| Mapperly         |  9.015 ns | 1.64x slower |      72 B |  2.25x more |\n| Mapster | 17.743 ns | 3.23x slower |      72 B |  2.25x more |\n| AutoMapper       | 36.794 ns | 6.69x slower |      72 B |  2.25x more |\n\n\n\n## Contributors\n\nFacet wouldn't be the same without these awesome contributors. Thank you!\n\n\u003ca href=\"https://github.com/Tim-Maes/Facet/graphs/contributors\"\u003e\n  \u003cimg src=\"https://contrib.rocks/image?repo=Tim-Maes/Facet\" /\u003e\n\u003c/a\u003e\n\n## 💖 Backers \u0026 supporters\n\n\u003ca href=\"https://github.com/pokeparadox\"\u003e\n  \u003cimg src=\"https://images.weserv.nl/?url=github.com/pokeparadox.png\u0026w=64\u0026h=64\u0026mask=circle\" width=\"64\" height=\"64\" /\u003e\n\u003c/a\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftim-maes%2Ffacet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftim-maes%2Ffacet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftim-maes%2Ffacet/lists"}