-
-
Notifications
You must be signed in to change notification settings - Fork 51
18_InheritanceMapping
Facet fully supports inheritance hierarchies in both source types and facet types. This guide covers how to work with inherited properties, base classes, and polymorphic scenarios.
When you create a facet from a source type that has a base class, Facet automatically includes all inherited properties from the entire inheritance chain. Similarly, your facet types can inherit from base classes to share common properties.
// Base domain model
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; } // Sensitive
public DateTime DateOfBirth { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
}
// Derived domain model
public class Employee : User
{
public string EmployeeId { get; set; }
public string Department { get; set; }
public DateTime HireDate { get; set; }
public decimal Salary { get; set; } // Sensitive
}
// Further derived
public class Manager : Employee
{
public string TeamName { get; set; }
public int TeamSize { get; set; }
public decimal Budget { get; set; } // Sensitive
}When you create a facet for Employee, it automatically includes properties from User:
// Facet for Employee - excludes sensitive properties from all levels
[Facet(typeof(Employee), "Password", "Salary", "CreatedAt")]
public partial class EmployeeDto;
// Generated properties include:
// From User: Id, FirstName, LastName, Email, DateOfBirth, IsActive
// From Employee: EmployeeId, Department, HireDate
// Excluded: Password (User), Salary (Employee), CreatedAt (User)For Manager, exclude sensitive properties from all inheritance levels:
[Facet(typeof(Manager), "Password", "Salary", "Budget", "CreatedAt")]
public partial class ManagerDto;
// Generated properties include:
// From User: Id, FirstName, LastName, Email, DateOfBirth, IsActive
// From Employee: EmployeeId, Department, HireDate
// From Manager: TeamName, TeamSize
// Excluded: Password, Salary, Budget, CreatedAtYour facet types can also use inheritance to share common properties and avoid duplication.
// Abstract base class for shared properties
public abstract class BaseFacet
{
public int Id { get; set; }
public bool IsActive { get; set; }
}
// Or with common audit properties
public abstract class AuditableFacet : BaseFacet
{
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
}When your facet inherits from a base class, Facet automatically detects inherited properties and won't generate duplicates:
// Entity with all properties
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public bool IsActive { get; set; }
public string InternalCode { get; set; } // Internal only
}
// Base facet class
public abstract class BaseFacet
{
public int Id { get; set; }
public bool IsActive { get; set; }
}
// Facet that inherits from base - Id and IsActive come from base class
[Facet(typeof(Product), "InternalCode")]
public partial class ProductDto : BaseFacet
{
// Generated properties: Name, Description, Price
// Inherited from BaseFacet: Id, IsActive (NOT duplicated)
}You can verify that properties aren't duplicated:
var facetType = typeof(ProductDto);
var declaredProperties = facetType.GetProperties(
BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
// Id and IsActive should NOT be in declared properties (they're inherited)
var propertyNames = declaredProperties.Select(p => p.Name).ToList();
Assert.DoesNotContain("Id", propertyNames);
Assert.DoesNotContain("IsActive", propertyNames);
// Name, Description, Price should be declared
Assert.Contains("Name", propertyNames);
Assert.Contains("Description", propertyNames);
Assert.Contains("Price", propertyNames);Facet also handles generic base classes correctly:
// Generic base entity
public class BaseEntity<TKey>
{
public TKey Id { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
}
// Concrete entity
public class Category : BaseEntity<uint>
{
public string Name { get; set; }
public string Description { get; set; }
}
// Facet excluding Id (inherited from generic base)
[Facet(typeof(Category), "Id")]
public partial class UpdateCategoryDto;
// Result: Name, Description, CreatedAt, UpdatedAt (Id excluded)For polymorphic scenarios where you have a collection of base types containing derived instances:
// Map each type to its specific facet
var users = GetAllUsers(); // Returns User, Employee, and Manager instances
// Option 1: Map all to base facet type
var userDtos = users.Select(u => u.ToFacet<User, UserDto>()).ToList();
// Option 2: Use pattern matching for type-specific facets
var results = users.Select(u => u switch
{
Manager m => (object)m.ToFacet<Manager, ManagerDto>(),
Employee e => (object)e.ToFacet<Employee, EmployeeDto>(),
User user => (object)user.ToFacet<User, UserDto>()
}).ToList();When using Include mode with inherited properties, you can include properties from any level of the inheritance hierarchy:
// Include specific properties from both User and Employee
[Facet(typeof(Employee),
Include = [
nameof(User.Id),
nameof(User.FirstName),
nameof(User.LastName),
nameof(Employee.Department),
nameof(Employee.HireDate)
])]
public partial class EmployeeBasicDto;When mapping derived types, the generated constructor and projection properly access all inherited properties:
// Generated constructor (simplified)
public EmployeeDto(Employee source)
{
// Properties from User (base class)
this.Id = source.Id;
this.FirstName = source.FirstName;
this.LastName = source.LastName;
this.Email = source.Email;
this.DateOfBirth = source.DateOfBirth;
this.IsActive = source.IsActive;
// Properties from Employee
this.EmployeeId = source.EmployeeId;
this.Department = source.Department;
this.HireDate = source.HireDate;
}
// Generated projection
public static Expression<Func<Employee, EmployeeDto>> Projection =>
source => new EmployeeDto
{
Id = source.Id,
FirstName = source.FirstName,
// ... all properties including inherited
};When creating facets for derived types, remember to exclude sensitive properties from all levels of the hierarchy:
// Good: Exclude from all levels
[Facet(typeof(Manager), "Password", "Salary", "Budget")]
public partial class ManagerDto;
// Avoid: Forgetting base class sensitive properties
[Facet(typeof(Manager), "Budget")] // Password and Salary still exposed!
public partial class ManagerDto;// Define base facet with common properties
public abstract class BaseUserFacet
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool IsActive { get; set; }
}
// All user-related facets inherit from base
[Facet(typeof(User), "Password", "CreatedAt")]
public partial class UserDto : BaseUserFacet;
[Facet(typeof(Employee), "Password", "Salary", "CreatedAt")]
public partial class EmployeeDto : BaseUserFacet;// Public API response - minimal data
[Facet(typeof(Employee), Include = ["Id", "FirstName", "LastName", "Department"])]
public partial class EmployeePublicDto;
// Internal/Admin response - more data but still no passwords
[Facet(typeof(Employee), "Password")]
public partial class EmployeeAdminDto;
// HR response - includes salary
[Facet(typeof(Employee), "Password")]
public partial class EmployeeHrDto;-
Abstract Source Types: You cannot create a facet directly from an abstract class (no instances to map). Create facets for concrete derived types instead.
-
Interface Source Types: Facet works with classes and structs, not interfaces. Define facets for implementing classes.
-
Multiple Inheritance: C# doesn't support multiple inheritance, so facet types can only inherit from a single base class (plus interfaces).