This page shows concrete examples of what Facet generates for different scenarios. These examples are based on the actual test suite and reflect the current version of Facet.
Note: The generated code includes comprehensive XML documentation comments. These are omitted in some examples below for brevity, but they are present in the actual generated code.
Input:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public DateTime CreatedAt { get; set; }
}
[Facet(typeof(User), "Password", "CreatedAt", GenerateToSource = true)]
public partial class UserDto
{
public string FullName { get; set; } = string.Empty;
public int Age { get; set; }
}Generated:
public partial class UserDto
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="UserDto"/> class from the specified <see cref="User"/>.
/// </summary>
public UserDto(User source) : this(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance))
{
}
/// <summary>
/// Constructor with depth tracking to prevent stack overflow from circular references.
/// </summary>
public UserDto(User source, int __depth, HashSet<object>? __processed)
{
this.Id = source.Id;
this.FirstName = source.FirstName;
this.LastName = source.LastName;
this.Email = source.Email;
}
/// <summary>
/// Creates a new instance of <see cref="UserDto"/> from the specified <see cref="User"/>.
/// </summary>
public static UserDto FromSource(User source)
{
return new UserDto(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance));
}
/// <summary>
/// Initializes a new instance of the <see cref="UserDto"/> class with default values.
/// </summary>
public UserDto()
{
}
/// <summary>
/// Gets the projection expression for converting <see cref="User"/> to <see cref="UserDto"/>.
/// Use this for LINQ and Entity Framework query projections.
/// </summary>
public static Expression<Func<User, UserDto>> Projection =>
source => new UserDto
{
Id = source.Id,
FirstName = source.FirstName,
LastName = source.LastName,
Email = source.Email
};
/// <summary>
/// Converts this instance of <see cref="UserDto"/> to an instance of the source type.
/// </summary>
public User ToSource()
{
return new User
{
Id = this.Id,
FirstName = this.FirstName,
LastName = this.LastName,
Email = this.Email
};
}
[Obsolete("Use ToSource() instead. This method will be removed in a future version.")]
public User BackTo() => ToSource();
}What's Generated:
- All properties from source except excluded ones ("Password", "CreatedAt")
- User-defined properties from partial class ("FullName", "Age") are preserved
- Constructor with circular reference protection
FromSource()static factory method for optimal performance- Parameterless constructor for deserialization
Projectionexpression for LINQ/EF queriesToSource()method for reverse mapping (becauseGenerateToSource = true)- Obsolete
BackTo()method for backward compatibility
Input:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
}
[Facet(typeof(User), Include = [nameof(User.FirstName), nameof(User.LastName), nameof(User.Email)], GenerateToSource = true)]
public partial class UserIncludeDto;Generated:
public partial class UserIncludeDto
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public UserIncludeDto(User source) : this(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance))
{
}
public UserIncludeDto(User source, int __depth, HashSet<object>? __processed)
{
this.FirstName = source.FirstName;
this.LastName = source.LastName;
this.Email = source.Email;
}
public static UserIncludeDto FromSource(User source)
{
return new UserIncludeDto(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance));
}
public UserIncludeDto()
{
}
public static Expression<Func<User, UserIncludeDto>> Projection =>
source => new UserIncludeDto
{
FirstName = source.FirstName,
LastName = source.LastName,
Email = source.Email
};
public User ToSource()
{
return new User
{
FirstName = this.FirstName,
LastName = this.LastName,
Email = this.Email
};
}
[Obsolete("Use ToSource() instead.")]
public User BackTo() => ToSource();
}Key Point: Include mode generates ToSource() automatically, initializing excluded properties with their default values.
Input:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
}
public class UserDtoMapper : IFacetMapConfiguration<User, UserDto>
{
public static void Map(User source, UserDto target)
{
target.FullName = $"{source.FirstName} {source.LastName}";
target.Age = CalculateAge(source.DateOfBirth);
}
private static int CalculateAge(DateTime birthDate)
{
var today = DateTime.Today;
var age = today.Year - birthDate.Year;
if (birthDate.Date > today.AddYears(-age)) age--;
return age;
}
}
[Facet(typeof(User), "Password", "CreatedAt", Configuration = typeof(UserDtoMapper))]
public partial class UserDto
{
public string FullName { get; set; } = string.Empty;
public int Age { get; set; }
}Generated:
public partial class UserDto
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public UserDto(User source) : this(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance))
{
}
public UserDto(User source, int __depth, HashSet<object>? __processed)
{
this.Id = source.Id;
this.FirstName = source.FirstName;
this.LastName = source.LastName;
this.DateOfBirth = source.DateOfBirth;
UserDtoMapper.Map(source, this); // Custom mapping applied after property copying
}
public static UserDto FromSource(User source)
{
return new UserDto(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance));
}
public UserDto()
{
}
public static Expression<Func<User, UserDto>> Projection =>
source => new UserDto
{
Id = source.Id,
FirstName = source.FirstName,
LastName = source.LastName,
DateOfBirth = source.DateOfBirth
};
}Note: Custom mapping is applied in the constructor but NOT in the Projection expression (since expressions can't call arbitrary methods).
Input:
public record ClassicUser(string Id, string FirstName, string LastName, string? Email);
[Facet(typeof(ClassicUser), GenerateToSource = true)]
public partial record ClassicUserDto;Generated:
public partial record ClassicUserDto(string Id, string FirstName, string LastName, string? Email);
public partial record ClassicUserDto
{
[SetsRequiredMembers]
public ClassicUserDto(ClassicUser source) : this(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance))
{
}
[SetsRequiredMembers]
public ClassicUserDto(ClassicUser source, int __depth, HashSet<object>? __processed)
: this(source.Id, source.FirstName, source.LastName, source.Email)
{
}
public ClassicUserDto() : this(string.Empty, string.Empty, string.Empty, null)
{
}
public static Expression<Func<ClassicUser, ClassicUserDto>> Projection =>
source => new ClassicUserDto
{
Id = source.Id,
FirstName = source.FirstName,
LastName = source.LastName,
Email = source.Email
};
public ClassicUser ToSource()
{
return new ClassicUser(this.Id, this.FirstName, this.LastName, this.Email);
}
[Obsolete("Use ToSource() instead.")]
public ClassicUser BackTo() => ToSource();
}Note: Records generate positional parameters in addition to the standard members.
Input:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public bool IsAvailable { get; set; }
public DateTime CreatedAt { get; set; }
}
[Facet(typeof(Product), "InternalNotes", "CreatedAt", NullableProperties = true, GenerateToSource = false)]
public partial class ProductQueryDto;Generated:
public partial class ProductQueryDto
{
public int? Id { get; set; }
public string? Name { get; set; }
public decimal? Price { get; set; }
public bool? IsAvailable { get; set; }
public ProductQueryDto(Product source) : this(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance))
{
}
public ProductQueryDto(Product source, int __depth, HashSet<object>? __processed)
{
this.Id = source.Id;
this.Name = source.Name;
this.Price = source.Price;
this.IsAvailable = source.IsAvailable;
}
public static ProductQueryDto FromSource(Product source)
{
return new ProductQueryDto(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance));
}
public ProductQueryDto()
{
}
public static Expression<Func<Product, ProductQueryDto>> Projection =>
source => new ProductQueryDto
{
Id = source.Id,
Name = source.Name,
Price = source.Price,
IsAvailable = source.IsAvailable
};
// Note: No ToSource() method generated when GenerateToSource = false
}Why nullable properties? All value types become nullable (int -> int?, bool -> bool?, decimal -> decimal?) and reference types are marked nullable. Perfect for query/filter scenarios where all criteria are optional.
Input:
public class MapFromTestEntity
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
[Facet(typeof(MapFromTestEntity), GenerateToSource = true)]
public partial class MapFromSimpleFacet
{
[MapFrom(nameof(MapFromTestEntity.FirstName), Reversible = true)]
public string Name { get; set; } = string.Empty;
}Generated:
public partial class MapFromSimpleFacet
{
public int Id { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public MapFromSimpleFacet(MapFromTestEntity source) : this(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance))
{
}
public MapFromSimpleFacet(MapFromTestEntity source, int __depth, HashSet<object>? __processed)
{
this.Id = source.Id;
this.LastName = source.LastName;
this.Email = source.Email;
this.Name = source.FirstName; // Mapped from FirstName
}
public static MapFromSimpleFacet FromSource(MapFromTestEntity source)
{
return new MapFromSimpleFacet(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance));
}
public MapFromSimpleFacet()
{
}
public static Expression<Func<MapFromTestEntity, MapFromSimpleFacet>> Projection =>
source => new MapFromSimpleFacet
{
Id = source.Id,
LastName = source.LastName,
Email = source.Email,
Name = source.FirstName // Mapped in projection too
};
public MapFromTestEntity ToSource()
{
return new MapFromTestEntity
{
Id = this.Id,
LastName = this.LastName,
Email = this.Email,
FirstName = this.Name // Reverse mapping (because Reversible = true)
};
}
[Obsolete("Use ToSource() instead.")]
public MapFromTestEntity BackTo() => ToSource();
}Key Points:
MapFromrenames properties during mappingReversible = trueenables reverse mapping inToSource()Reversible = false(default) means property won't be mapped inToSource()IncludeInProjection = falseexcludes the property from theProjectionexpression
Input:
public class MapWhenTestEntity
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public OrderStatus Status { get; set; }
public string? Email { get; set; }
public int Age { get; set; }
}
public enum OrderStatus
{
Pending,
Processing,
Completed,
Cancelled
}
[Facet(typeof(MapWhenTestEntity))]
public partial class MapWhenMixedFacet
{
[MapWhen("IsActive")]
public string? Email { get; set; }
[MapWhen("Status == OrderStatus.Completed")]
public DateTime? CompletedAt { get; set; }
}Generated:
public partial class MapWhenMixedFacet
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public OrderStatus Status { get; set; }
public int Age { get; set; }
public MapWhenMixedFacet(MapWhenTestEntity source) : this(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance))
{
}
public MapWhenMixedFacet(MapWhenTestEntity source, int __depth, HashSet<object>? __processed)
{
this.Id = source.Id;
this.Name = source.Name;
this.IsActive = source.IsActive;
this.Status = source.Status;
this.Age = source.Age;
// Conditional mapping
if (source.IsActive)
{
this.Email = source.Email;
}
if (source.Status == OrderStatus.Completed)
{
this.CompletedAt = source.CompletedAt;
}
}
public static MapWhenMixedFacet FromSource(MapWhenTestEntity source)
{
return new MapWhenMixedFacet(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance));
}
public MapWhenMixedFacet()
{
}
public static Expression<Func<MapWhenTestEntity, MapWhenMixedFacet>> Projection =>
source => new MapWhenMixedFacet
{
Id = source.Id,
Name = source.Name,
IsActive = source.IsActive,
Status = source.Status,
Age = source.Age,
Email = source.IsActive ? source.Email : default,
CompletedAt = source.Status == OrderStatus.Completed ? source.CompletedAt : default
};
}Key Points:
MapWhenadds conditional logic to property mapping- Supports boolean properties, equality checks, null checks, comparisons
- Can use multiple
[MapWhen]attributes for AND logic IncludeInProjection = falseexcludes conditional from projection
Input:
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public decimal Salary { get; set; }
}
[Wrapper(typeof(User), "Password", "Salary")]
public partial class PublicUserWrapper { }Generated:
public partial class PublicUserWrapper
{
private readonly User _source;
public PublicUserWrapper(User source)
{
_source = source ?? throw new ArgumentNullException(nameof(source));
}
public int Id
{
get => _source.Id;
set => _source.Id = value;
}
public string FirstName
{
get => _source.FirstName;
set => _source.FirstName = value;
}
public string LastName
{
get => _source.LastName;
set => _source.LastName = value;
}
public string Email
{
get => _source.Email;
set => _source.Email = value;
}
// Password and Salary are excluded - no properties generated
public User Unwrap() => _source;
}Key Points:
Wrappercreates a delegation wrapper, not a copy- Changes to wrapper properties affect the source object
Unwrap()returns the original source object- Useful for hiding sensitive properties without copying data
Input:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
public ContactInfo ContactInfo { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
public Country Country { get; set; }
}
public class Country
{
public string Name { get; set; }
public string Code { get; set; }
}
public class ContactInfo
{
public string Email { get; set; }
public string Phone { get; set; }
}
[Flatten(typeof(Person))]
public partial class PersonFlatDto;Generated:
public partial class PersonFlatDto
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
// Flattened from Address
public string AddressStreet { get; set; }
public string AddressCity { get; set; }
public string AddressZipCode { get; set; }
// Flattened from Address.Country
public string AddressCountryName { get; set; }
public string AddressCountryCode { get; set; }
// Flattened from ContactInfo
public string ContactInfoEmail { get; set; }
public string ContactInfoPhone { get; set; }
public PersonFlatDto(Person source)
{
this.Id = source.Id;
this.FirstName = source.FirstName;
this.LastName = source.LastName;
if (source.Address != null)
{
this.AddressStreet = source.Address.Street;
this.AddressCity = source.Address.City;
this.AddressZipCode = source.Address.ZipCode;
if (source.Address.Country != null)
{
this.AddressCountryName = source.Address.Country.Name;
this.AddressCountryCode = source.Address.Country.Code;
}
}
if (source.ContactInfo != null)
{
this.ContactInfoEmail = source.ContactInfo.Email;
this.ContactInfoPhone = source.ContactInfo.Phone;
}
}
public PersonFlatDto()
{
}
}Key Points:
Flattenrecursively flattens nested object properties- Naming strategy:
ParentPropertyChildProperty(can be customized) MaxDepthparameter controls nesting depthIgnoreNestedIdsexcludes nested Id propertiesNamingStrategy = FlattenNamingStrategy.LeafOnlyorSmartLeaffor different naming- Collections are ignored by default
Input:
public class UserForNestedFacet
{
public int Id { get; set; }
public string Name { get; set; }
public UserAddressForNestedFacet Address { get; set; }
}
public class UserAddressForNestedFacet
{
public string Street { get; set; }
public string City { get; set; }
public string FormattedAddress => $"{Street}, {City}";
}
[Facet(typeof(UserForNestedFacet), Include = [
nameof(UserForNestedFacet.Id),
nameof(UserForNestedFacet.Name),
nameof(UserForNestedFacet.Address)
], NestedFacets = [typeof(UserDetailResponse.UserAddressItem)])]
public partial class UserDetailResponse
{
[Facet(typeof(UserAddressForNestedFacet), Include = [
nameof(UserAddressForNestedFacet.FormattedAddress)
])]
public partial class UserAddressItem;
}Generated:
// UserAddressItem is generated as a nested class
public partial class UserDetailResponse
{
public partial class UserAddressItem
{
public string FormattedAddress { get; set; }
public UserAddressItem(UserAddressForNestedFacet source) : this(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance))
{
}
public UserAddressItem(UserAddressForNestedFacet source, int __depth, HashSet<object>? __processed)
{
this.FormattedAddress = source.FormattedAddress;
}
public static UserAddressItem FromSource(UserAddressForNestedFacet source)
{
return new UserAddressItem(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance));
}
public UserAddressItem()
{
}
public static Expression<Func<UserAddressForNestedFacet, UserAddressItem>> Projection =>
source => new UserAddressItem
{
FormattedAddress = source.FormattedAddress
};
}
}
// UserDetailResponse uses the nested facet
public partial class UserDetailResponse
{
public int Id { get; set; }
public string Name { get; set; }
public UserAddressItem? Address { get; set; }
public UserDetailResponse(UserForNestedFacet source) : this(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance))
{
}
public UserDetailResponse(UserForNestedFacet source, int __depth, HashSet<object>? __processed)
{
this.Id = source.Id;
this.Name = source.Name;
this.Address = source.Address != null ? new UserAddressItem(source.Address, __depth + 1, __processed) : null;
}
public static UserDetailResponse FromSource(UserForNestedFacet source)
{
return new UserDetailResponse(source, 0, new HashSet<object>(ReferenceEqualityComparer.Instance));
}
public UserDetailResponse()
{
}
public static Expression<Func<UserForNestedFacet, UserDetailResponse>> Projection =>
source => new UserDetailResponse
{
Id = source.Id,
Name = source.Name,
Address = source.Address != null ? new UserAddressItem(source.Address) : null
};
}Key Points:
- Nested facets create nested DTOs
- Depth tracking prevents infinite recursion for circular references
- Nested facets are specified via
NestedFacetsparameter
Facet provides fine-grained control over what gets generated:
[Facet(typeof(Source),
GenerateConstructor = false, // Skip source constructor
GenerateParameterlessConstructor = false, // Skip parameterless constructor
GenerateProjection = false, // Skip Projection expression
GenerateToSource = false)] // Skip ToSource() method
public partial class MyDto;Common Combinations:
- Query DTO:
NullableProperties = true, GenerateToSource = false - Response DTO:
GenerateToSource = false(read-only) - Manual construction:
GenerateConstructor = false(for custom logic) - EF projections only:
GenerateConstructor = false(only useProjection)
For a basic facet, Facet generates:
- All non-excluded properties from source
- User-defined properties from partial class
- XML documentation copied from source
Dto(Source source)- primary constructorDto(Source source, int __depth, HashSet<object>? __processed)- depth-tracking constructorDto()- parameterless constructor (optional)
static Dto FromSource(Source source)- factory method for optimal runtime performanceSource ToSource()- reverse mapping (whenGenerateToSource = true)Source BackTo()- obsolete, callsToSource()Source Unwrap()- for Wrapper only
static Expression<Func<Source, Dto>> Projection- for LINQ/EF queries
If you're upgrading from an earlier version of Facet, here are the major changes to generated code:
- Circular reference protection: Constructors now include
__depthand__processedparameters FromSource()method: New static factory method for optimal performanceToSource()replacesBackTo():BackTo()is now obsolete- Comprehensive XML documentation: All generated members include XML docs
- Parameterless constructor: Now generated by default
- New attributes:
MapFrom,MapWhen,Wrapper,Flattenadded [SetsRequiredMembers]attribute: Used on constructors for records with required members
See also:
- Quick Start - Basic usage and getting started
- Attribute Reference - Complete attribute documentation
- Advanced Scenarios - Complex mapping scenarios