Facet : générateur de projections pour Entity Framework

Facet automatise la création de DTOs et de mappings pour vos entités Entity Framework, réduisant le code répétitif.

Installation

Installez le package NuGet Facet et référencez‑le dans votre projet pour activer le générateur de code.

dotnet add package Facet --version 2.5.1

Utilisation de base

Ajoutez l'attribut [Facet] sur une classe partielle pour générer automatiquement les propriétés et la projection.

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
    public List<Order> Orders { get; set; }
}

// Définition de la projection avec l'attribut [Facet]
[Facet]
public partial class UserDto
{
    // Propriétés définies manuellement
    public int Id { get; set; }
    public string Name { get; set; }
    
    // Propriétés générées automatiquement par Facet
    // basées sur la classe User
}

Code généré

Facet génère automatiquement les propriétés manquantes et une expression de projection pour mapper efficacement vos entités.

// Code généré automatiquement par Facet
partial class UserDto
{
    public string Email { get; set; }
    public DateTime CreatedAt { get; set; }
    
    // Expression de projection générée
    public static Expression<Func<User, UserDto>> Projection =>
        user => new UserDto
        {
            Id = user.Id,
            Name = user.Name,
            Email = user.Email,
            CreatedAt = user.CreatedAt
        };
    
    // Méthode de projection
    public static UserDto FromUser(User user) =>
        new UserDto
        {
            Id = user.Id,
            Name = user.Name,
            Email = user.Email,
            CreatedAt = user.CreatedAt
        };
}

Intégration avec Entity Framework

Utilisez les expressions de projection générées directement dans vos requêtes LINQ pour traduire en SQL optimal.

public class UserService
{
    private readonly ApplicationDbContext _context;
    
    public UserService(ApplicationDbContext context)
    {
        _context = context;
    }
    
    // Utilisation avec Entity Framework
    public async Task<List<UserDto>> GetUsersAsync()
    {
        return await _context.Users
            .Select(UserDto.Projection)
            .ToListAsync();
    }
    
    // Plus efficace que :
    // return await _context.Users
    //     .ToListAsync()
    //     .Select(user => new UserDto { ... });
}

Projections complexes

Composez des DTOs avancés, ajoutez des propriétés calculées et naviguez entre projections pour des vues riches.

[Facet]
public partial class UserDetailDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    
    // Propriété calculée personnalisée
    public string DisplayName => $"{Name} ({Email})";
    
    // Navigation vers une autre projection
    public List<OrderDto> RecentOrders { get; set; }
    
    // Propriété conditionnelle
    [ConditionalProperty]
    public bool IsActive => CreatedAt > DateTime.UtcNow.AddMonths(-6);
}

[Facet]
public partial class OrderDto
{
    public int Id { get; set; }
    public decimal Amount { get; set; }
    public DateTime OrderDate { get; set; }
}

Configuration avancée

Contrôlez finement la génération via des attributs (Ignore, Property, IncludeNavigationProperties, etc.).

[Facet(
    SourceType = typeof(User),
    IncludeNavigationProperties = false,
    GenerateMapper = true)]
public partial class UserSummaryDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    
    // Exclusion explicite d'une propriété
    [FacetIgnore]
    public string Email { get; set; }
    
    // Mapping personnalisé
    [FacetProperty(SourceProperty = "Email")]
    public string ContactEmail { get; set; }
}

Performance

Les projections générées s'exécutent côté base de données via LINQ to Entities, réduisant le volume de données transférées.

// ❌ Inefficace - charge toutes les données en mémoire
var users = await _context.Users
    .Include(u => u.Orders)
    .ToListAsync();
var userDtos = users.Select(u => new UserDto 
{ 
    Id = u.Id, 
    Name = u.Name 
}).ToList();

// ✅ Efficace - projection au niveau SQL
var userDtos = await _context.Users
    .Select(UserDto.Projection)
    .ToListAsync();

// SQL généré :
// SELECT [u].[Id], [u].[Name] 
// FROM [Users] AS [u]

Bonnes pratiques

  • Nommez clairement vos DTOs (Suffixe Dto/ReadModel)

  • Exposez uniquement les propriétés utiles

  • Une projection par cas d'usage

  • Intégrez la génération au build CI

  • Documentez les champs calculés

Alternatives

AutoMapper, Mapster ou la projection manuelle via LINQ sont des options si vous ne souhaitez pas de génération de code.

// AutoMapper
var config = new MapperConfiguration(cfg => {
    cfg.CreateMap<User, UserDto>();
});

// Mapster
var userDto = user.Adapt<UserDto>();

// Projection manuelle avec Entity Framework
var userDtos = await _context.Users
    .Select(u => new UserDto
    {
        Id = u.Id,
        Name = u.Name,
        Email = u.Email
    })
    .ToListAsync();

Conclusion

Facet accélère la création de DTOs performants et maintenables. Il s'intègre naturellement avec EF Core via des projections typées.