Facet Analyzer Rules
July 28, 2026 · View on GitHub
Facet includes comprehensive Roslyn analyzers that provide real-time feedback in your IDE. These analyzers catch common mistakes and configuration issues at design-time, before you even compile your code.
Quick Reference
| Rule ID | Severity | Category | Description |
|---|---|---|---|
| FAC001 | Error | Usage | Type must be annotated with [Facet] |
| FAC002 | Info | Performance | Consider using two-generic variant |
| FAC003 | Error | Declaration | Missing partial keyword on [Facet] type |
| FAC004 | Error | Usage | Invalid property name in Exclude/Include |
| FAC005 | Error | Usage | Invalid source type |
| FAC006 | Error | Usage | Invalid Configuration type |
| FAC007 | Warning | Usage | Invalid NestedFacets type |
| FAC008 | Warning | Performance | Circular reference risk |
| FAC009 | Error | Usage | Both Include and Exclude specified |
| FAC010 | Warning | Performance | Unusual MaxDepth value |
| FAC011 | Error | Usage | [GenerateDtos] on non-class type |
| FAC012 | Warning | Usage | Invalid ExcludeProperties |
| FAC013 | Warning | Usage | No DTO types selected |
| FAC014 | Error | Declaration | Missing partial keyword on [Flatten] type |
| FAC015 | Error | Usage | Invalid source type in [Flatten] |
| FAC016 | Warning | Performance | Unusual MaxDepth in [Flatten] |
| FAC017 | Info | Usage | LeafOnly naming collision risk |
| FAC022 | Warning | SourceTracking | Source entity structure changed |
| FAC101 | Error | Generator | OutputType combines multiple concrete kinds |
| FAC102 | Error | Generator | OutputType sets Partial without a kind |
| FAC103 | Error | Generator | EF model manifest could not be read |
| FAC104 | Error | Generator | EF model manifest version not supported |
| FAC105 | Error | Generator | Source type not in the EF model manifest |
| FAC106 | Warning | Generator | Property unknown to the EF model manifest |
Extension Method Analyzers
FAC001
Type must be annotated with [Facet]
- Severity: Error
- Category: Usage
Description
When using extension methods like ToFacet<T>(), ToSource<T>(), SelectFacet<T>(), etc., the target type must be annotated with the [Facet] attribute.
Bad Code
// UserDto does NOT have [Facet] attribute
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
}
var dto = user.ToFacet<User, UserDto>(); // ❌ FAC001
Good Code
[Facet(typeof(User))]
public partial class UserDto { }
var dto = user.ToFacet<User, UserDto>(); // ✅ OK
FAC002
Consider using the two-generic variant for better performance
- Severity: Info
- Category: Performance
Description
When using single-generic extension methods like ToFacet<TTarget>(), the library uses reflection to discover the source type. For better performance, use the two-generic variant ToFacet<TSource, TTarget>().
Code Triggering Warning
var dto = user.ToFacet<UserDto>(); // ℹ️ FAC002: Consider ToFacet<User, UserDto>()
Recommended Code
var dto = user.ToFacet<User, UserDto>(); // ✅ Better performance
Impact
The performance difference is minimal (a few nanoseconds) but can add up in tight loops or high-throughput scenarios.
[Facet] Attribute Analyzers
FAC003
Type with [Facet] attribute must be declared as partial
- Severity: Error
- Category: Declaration
Description
Source generators require types to be partial so they can add generated members. Any type marked with [Facet] must be declared as partial.
Bad Code
[Facet(typeof(User))]
public class UserDto { } // ❌ FAC003: Missing 'partial' keyword
Good Code
[Facet(typeof(User))]
public partial class UserDto { } // ✅ OK
FAC004
Property name does not exist in source type
- Severity: Error
- Category: Usage
Description
Property names specified in Exclude or Include parameters must exist in the source type.
Bad Code
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
[Facet(typeof(User), "PasswordHash")] // ❌ FAC004: User doesn't have PasswordHash
public partial class UserDto { }
Good Code
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string PasswordHash { get; set; }
}
[Facet(typeof(User), "PasswordHash")] // ✅ OK
public partial class UserDto { }
FAC005
Source type is not accessible or does not exist
- Severity: Error
- Category: Usage
Description
The source type specified in the [Facet] attribute must be a valid, accessible type.
Bad Code
[Facet(typeof(NonExistentType))] // ❌ FAC005
public partial class UserDto { }
Good Code
[Facet(typeof(User))] // ✅ OK
public partial class UserDto { }
FAC006
Configuration type does not implement required interface
- Severity: Error
- Category: Usage
Description
Configuration types must implement IFacetMapConfiguration<TSource, TTarget>, IFacetMapConfigurationAsync<TSource, TTarget>, IFacetProjectionMapConfiguration<TSource, TTarget>, or provide a static Map method.
Bad Code
public class UserMapper // ❌ No interface, no Map method
{
public void DoSomething(User source, UserDto target) { }
}
[Facet(typeof(User), Configuration = typeof(UserMapper))] // ❌ FAC006
public partial class UserDto { }
Good Code
// Option 1: Implement IFacetMapConfiguration
public class UserMapper : IFacetMapConfiguration<User, UserDto>
{
public static void Map(User source, UserDto target)
{
target.FullName = $"{source.FirstName} {source.LastName}";
}
}
// Option 2: Implement IFacetProjectionMapConfiguration (expression-only, reused in constructors)
public class UserMapper : IFacetProjectionMapConfiguration<User, UserDto>
{
public static void ConfigureProjection(IFacetProjectionBuilder<User, UserDto> builder)
{
builder.Map(d => d.FullName, s => s.FirstName + " " + s.LastName);
}
}
// Option 3: Provide static Map method
public class UserMapper
{
public static void Map(User source, UserDto target)
{
target.FullName = $"{source.FirstName} {source.LastName}";
}
}
[Facet(typeof(User), Configuration = typeof(UserMapper))] // ✅ OK
public partial class UserDto { }
FAC007
Nested facet type is not marked with [Facet] attribute
- Severity: Warning
- Category: Usage
Description
All types specified in the NestedFacets array must be marked with the [Facet] attribute.
Bad Code
public class AddressDto { } // ❌ Missing [Facet] attribute
[Facet(typeof(User), NestedFacets = [typeof(AddressDto)])] // ⚠️ FAC007
public partial class UserDto { }
Good Code
[Facet(typeof(Address))]
public partial class AddressDto { }
[Facet(typeof(User), NestedFacets = [typeof(AddressDto)])] // ✅ OK
public partial class UserDto { }
FAC008
Potential stack overflow with circular references
- Severity: Warning
- Category: Performance
Description
When MaxDepth is set to 0 (unlimited) and PreserveReferences is false, circular references in object graphs can cause stack overflow exceptions.
Bad Code
[Facet(typeof(User),
MaxDepth = 0,
PreserveReferences = false,
NestedFacets = [typeof(CompanyDto)])] // ⚠️ FAC008
public partial class UserDto { }
Good Code
// Option 1: Enable PreserveReferences (default)
[Facet(typeof(User),
NestedFacets = [typeof(CompanyDto)])] // ✅ OK (PreserveReferences defaults to true)
// Option 2: Set MaxDepth limit
[Facet(typeof(User),
MaxDepth = 5,
NestedFacets = [typeof(CompanyDto)])] // ✅ OK
// Option 3: Both
[Facet(typeof(User),
MaxDepth = 10,
PreserveReferences = true,
NestedFacets = [typeof(CompanyDto)])] // ✅ OK (safest)
FAC009
Cannot specify both Include and Exclude
- Severity: Error
- Category: Usage
Description
The Include and Exclude parameters are mutually exclusive. Use either Include to whitelist properties or Exclude to blacklist properties, but not both.
Bad Code
[Facet(typeof(User),
nameof(User.PasswordHash), // Exclude parameter
Include = [nameof(User.Id), nameof(User.Name)])] // ❌ FAC009: Can't use both
public partial class UserDto { }
Good Code
// Option 1: Exclude approach
[Facet(typeof(User), nameof(User.PasswordHash), nameof(User.SecretKey))] // ✅ OK
public partial class UserDto { }
// Option 2: Include approach
[Facet(typeof(User), Include = [nameof(User.Id), nameof(User.Name), nameof(User.Email)])] // ✅ OK
public partial class UserDto { }
FAC010
MaxDepth value is unusual
- Severity: Warning
- Category: Performance
Description
MaxDepth values should typically be between 1 and 10 for most scenarios. Negative values are invalid, and values above 100 may indicate a configuration error.
Code Triggering Warning
[Facet(typeof(User), MaxDepth = -1)] // ⚠️ FAC010: Negative
[Facet(typeof(User), MaxDepth = 500)] // ⚠️ FAC010: Too large
Good Code
[Facet(typeof(User), MaxDepth = 5)] // ✅ OK
[Facet(typeof(User), MaxDepth = 10)] // ✅ OK (default)
[GenerateDtos] Attribute Analyzers
FAC011
[GenerateDtos] can only be applied to classes
- Severity: Error
- Category: Usage
Description
The [GenerateDtos] and [GenerateAuditableDtos] attributes are designed for class types and cannot be applied to structs, interfaces, or other type kinds.
Bad Code
[GenerateDtos(DtoTypes.All)]
public struct Product { } // ❌ FAC011: Can't use on struct
Good Code
[GenerateDtos(DtoTypes.All)]
public class Product { } // ✅ OK
FAC012
Excluded property does not exist
- Severity: Warning
- Category: Usage
Description
Properties specified in ExcludeProperties should exist in the source type.
Bad Code
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
[GenerateDtos(DtoTypes.All,
ExcludeProperties = ["InternalNotes"])] // ⚠️ FAC012: Doesn't exist
public class Product { }
Good Code
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string InternalNotes { get; set; }
}
[GenerateDtos(DtoTypes.All,
ExcludeProperties = ["InternalNotes"])] // ✅ OK
public class Product { }
FAC013
No DTO types selected for generation
- Severity: Warning
- Category: Usage
Description
Setting Types to DtoTypes.None will not generate any DTOs.
Bad Code
[GenerateDtos(Types = DtoTypes.None)] // ⚠️ FAC013: No DTOs will be generated
public class Product { }
Good Code
[GenerateDtos(Types = DtoTypes.All)] // ✅ OK
public class Product { }
// Or specify specific types
[GenerateDtos(Types = DtoTypes.Create | DtoTypes.Update | DtoTypes.Response)]
public class Product { }
FAC101
OutputType combines multiple concrete output kinds
- Severity: Error
- Category: Generator
OutputType is a flags value, but concrete kinds (Class, Record, Struct, RecordStruct) all generate identically-named types and would collide. Combine at most one concrete kind with OutputType.Interface and/or the Partial modifier.
[GenerateDtos(OutputType = OutputType.Class | OutputType.Record)] // ❌ FAC101
[GenerateDtos(OutputType = OutputType.Interface | OutputType.Record)] // ✅ OK
FAC102
OutputType sets the Partial modifier without an output kind
- Severity: Error
- Category: Generator
Partial only modifies how requested kinds are emitted; on its own there is nothing to generate.
[GenerateDtos(OutputType = OutputType.Partial)] // ❌ FAC102
[GenerateDtos(OutputType = OutputType.Record | OutputType.Partial)] // ✅ OK
FAC103
EF model manifest could not be read
- Severity: Error
- Category: Generator
A *.facetmodel.json file wired up as an AdditionalFile is not readable as a manifest (malformed JSON, wrong shape). The file is ignored in full — never half-applied — and reported here rather than silently skipped. A rejected file also does not count as a wired manifest, so it does not flip the ExcludeNavigationProperties default: this error stands alone instead of being buried under a FAC105 per source type. Regenerate the manifest (dotnet ef migrations add/remove) or remove it from AdditionalFiles.
FAC104
EF model manifest version is not supported
- Severity: Error
- Category: Generator
The manifest was written by a Facet.Extensions.EFCore version whose format this generator does not read — a package version mismatch. Align the Facet and Facet.Extensions.EFCore versions and regenerate the manifest.
FAC105
GenerateDtos source type is not in the EF model manifest
- Severity: Error
- Category: Generator
ExcludeNavigationProperties is driven entirely by the EF model manifest — there is no heuristic fallback — and it defaults to true for every [GenerateDtos] attribute in a project that wires a manifest into <AdditionalFiles>. A shaped source type with no manifest entry cannot be generated safely, so this is a hard error: the type is absent from the accepted manifests (regenerate with dotnet ef migrations add), or it is not an EF entity — set ExcludeNavigationProperties = false on it; an explicit value always wins over the wired-manifest default. For explicitly shaped types this error also catches a mistyped <AdditionalFiles> path, since a glob that matches nothing supplies no manifests at all. Under the wired-manifest default, a mistyped glob instead means no wired manifest, no shaping, and no diagnostic — pin ExcludeNavigationProperties = true on one representative entity if you want that failure loud. The message states whether the requirement came from an explicit ExcludeNavigationProperties = true or from the wired-manifest default.
FAC106
Property is unknown to the EF model manifest
- Severity: Warning
- Category: Generator
The manifest records every member the model has an opinion on (mapped, navigation, owned, skip navigation, ignored, service). A settable property outside that set is unknown to the model — almost always a property added after the manifest was last generated, which would otherwise silently vanish from generated DTOs. Regenerate the manifest, or mark the property [NotMapped]/Ignore() if the model genuinely does not map it (which also documents the intent). This stays a warning so mid-development edits (property added, migration not yet run) don't block the build; escalate for CI with <WarningsAsErrors>$(WarningsAsErrors);FAC106</WarningsAsErrors>.
[Flatten] Attribute Analyzers
FAC014
Type with [Flatten] attribute must be declared as partial
- Severity: Error
- Category: Declaration
Description
Similar to [Facet], types marked with [Flatten] must be partial.
Bad Code
[Flatten(typeof(Person))]
public class PersonFlat { } // ❌ FAC014
Good Code
[Flatten(typeof(Person))]
public partial class PersonFlat { } // ✅ OK
FAC015
Source type is not accessible or does not exist
- Severity: Error
- Category: Usage
Description
The source type specified in the [Flatten] attribute must be valid and accessible.
Bad Code
[Flatten(typeof(NonExistentType))] // ❌ FAC015
public partial class PersonFlat { }
Good Code
[Flatten(typeof(Person))] // ✅ OK
public partial class PersonFlat { }
FAC016
MaxDepth value is unusual
- Severity: Warning
- Category: Performance
Description
For flatten scenarios, MaxDepth values should typically be between 1 and 5. Values above 10 may cause excessive property generation.
Code Triggering Warning
[Flatten(typeof(Person), MaxDepth = -1)] // ⚠️ FAC016: Negative
[Flatten(typeof(Person), MaxDepth = 50)] // ⚠️ FAC016: Too large
Good Code
[Flatten(typeof(Person), MaxDepth = 3)] // ✅ OK (default)
[Flatten(typeof(Person), MaxDepth = 5)] // ✅ OK
FAC017
LeafOnly naming strategy may cause property name collisions
- Severity: Info
- Category: Usage
Description
Using FlattenNamingStrategy.LeafOnly can cause name collisions when multiple nested objects have properties with the same name. Consider using the Prefix strategy instead.
Code Triggering Warning
[Flatten(typeof(Person),
NamingStrategy = FlattenNamingStrategy.LeafOnly)] // ℹ️ FAC017
public partial class PersonFlat { }
Potential Issue
public class Person
{
public Address HomeAddress { get; set; }
public Address WorkAddress { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
}
// With LeafOnly, both addresses map to "Street" and "City" → collision!
Recommended Code
[Flatten(typeof(Person),
NamingStrategy = FlattenNamingStrategy.Prefix)] // ✅ Better
public partial class PersonFlat { }
// Generates: HomeAddressStreet, HomeAddressCity, WorkAddressStreet, WorkAddressCity
Source Signature Analyzers
FAC022
Source entity structure changed
- Severity: Warning
- Category: SourceTracking
Description
When you set SourceSignature on a [Facet] attribute, the analyzer computes a hash of the source type's properties and compares it to the stored signature. This warning is raised when the source entity's structure changes.
Code Triggering Warning
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; } // New property added
}
[Facet(typeof(User), SourceSignature = "oldvalue")] // ⚠️ FAC022
public partial class UserDto { }
Resolution
Use the provided code fix to update the signature, or manually update it:
[Facet(typeof(User), SourceSignature = "newvalue")] // ✅ OK
public partial class UserDto { }
Notes
- The signature is an 8-character hash computed from property names and types
- Respects
Include/Excludefilters when computing the signature - A code fix provider automatically offers to update the signature
- See Source Signature Change Tracking for details
Suppressing Analyzer Rules
If you need to suppress a specific analyzer rule, you can use:
In Code
#pragma warning disable FAC002
var dto = user.ToFacet<UserDto>();
#pragma warning restore FAC002
In .editorconfig
[*.cs]
dotnet_diagnostic.FAC002.severity = none
For Entire Project
<PropertyGroup>
<NoWarn>$(NoWarn);FAC002</NoWarn>
</PropertyGroup>
Configuration
All analyzers are enabled by default. You can configure their severity in your .editorconfig file:
[*.cs]
# Set a rule to error
dotnet_diagnostic.FAC007.severity = error
# Set a rule to warning
dotnet_diagnostic.FAC002.severity = warning
# Disable a rule
dotnet_diagnostic.FAC017.severity = none