C# Academy · Leçon

Filtres de requêtes globaux et entités possédées

Appliquez l’isolation des locataires et la suppression logique avec des filtres de requêtes globaux, puis mappez des objets-valeurs avec des types d’entités possédées.

Leçon 4 sur 413 étapes

Filtres de requêtes globaux et entités possédées est une leçon C# Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage C# Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours C# Academy comprend 4 leçons au total.

Que sont les filtres de requêtes globaux ?

Les filtres de requêtes globaux sont des prédicats WHERE appliqués automatiquement à chaque requête LINQ pour un type d'entité donné. Ils sont parfaits pour la suppression logique, la gestion de plusieurs locataires et la sécurité au niveau des lignes, sans avoir à répéter la condition partout.

Suppression logique avec un filtre global

Ajoutez un indicateur IsDeleted et configurez un filtre global afin que les enregistrements supprimés ne soient jamais renvoyés, sauf si vous ignorez explicitement le filtre.

// Entity with soft-delete flag
public class Post
{
    public int Id { get; set; }
    public string Title { get; set; } = "";
    public bool IsDeleted { get; set; }
}

// Configure in OnModelCreating
modelBuilder.Entity<Post>()
    .HasQueryFilter(p => !p.IsDeleted);

// All queries now implicitly add WHERE IsDeleted = 0:
var posts = await _db.Posts.ToListAsync();

Gestion de plusieurs locataires avec un filtre global

Utilisez un filtre qui lit le locataire actuel depuis un service associé à une portée. Chaque requête est automatiquement limitée aux données du locataire actuel.

public class AppDbContext : DbContext
{
    private readonly ICurrentUserService _user;

    public AppDbContext(DbContextOptions<AppDbContext> opt, ICurrentUserService user)
        : base(opt) => _user = user;

    protected override void OnModelCreating(ModelBuilder mb)
    {
        mb.Entity<Order>().HasQueryFilter(
            o => o.TenantId == _user.TenantId);
    }
}
// Every Order query automatically filters by TenantId

Ignorer les filtres globaux

Vous devez parfois contourner le filtre, par exemple pour les requêtes d'administration concernant tous les locataires ou pour restaurer un enregistrement supprimé logiquement. Utilisez IgnoreQueryFilters() uniquement pour cette requête.

// Admin: see ALL posts including deleted
var allPosts = await _db.Posts
    .IgnoreQueryFilters()
    .ToListAsync();

// Restore a specific deleted post
var deleted = await _db.Posts
    .IgnoreQueryFilters()
    .FirstOrDefaultAsync(p => p.Id == 42);

if (deleted is not null)
{
    deleted.IsDeleted = false;
    await _db.SaveChangesAsync();
}

Filtres sur les propriétés de navigation

Les filtres globaux participent également aux JOIN. Si les publications sont soumises à un filtre, les publications associées chargées via Include sont elles aussi filtrées automatiquement.

// Blog with soft-delete filter on Posts
var blog = await _db.Blogs
    .Include(b => b.Posts) // only non-deleted posts are included
    .FirstOrDefaultAsync(b => b.Id == 1);

// blog.Posts contains only active posts — the filter applied in the JOIN

Entités détenues : concept

Les types d'entités détenues sont des classes qui appartiennent exclusivement à une seule entité propriétaire. Elles n'ont pas de clé primaire et sont stockées dans la même table, par défaut. Elles sont parfaitement adaptées aux objets valeur en DDD.

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public Address BillingAddress  { get; set; } = new();
    public Address ShippingAddress { get; set; } = new();
}

public class Address
{
    public string Street  { get; set; } = "";
    public string City    { get; set; } = "";
    public string ZipCode { get; set; } = "";
    public string Country { get; set; } = "";
}

Configurer des entités détenues

Utilisez OwnsOne pour configurer une entité détenue unique. Par défaut, EF Core préfixe les colonnes avec le nom de la propriété de navigation.

modelBuilder.Entity<Customer>(entity =>
{
    entity.OwnsOne(c => c.BillingAddress, addr =>
    {
        addr.Property(a => a.Street).HasMaxLength(300);
        addr.Property(a => a.Country).HasMaxLength(2);
    });

    entity.OwnsOne(c => c.ShippingAddress);
});
// Table: Customers with columns:
// BillingAddress_Street, BillingAddress_City, ...
// ShippingAddress_Street, ShippingAddress_City, ...

Entités détenues dans une table distincte

Utilisez ToTable() dans OwnsOne pour stocker les entités détenues dans leur propre table, ce qui est utile lorsque les données sont volumineuses ou facultatives.

modelBuilder.Entity<Customer>().OwnsOne(
    c => c.BillingAddress,
    addr => addr.ToTable("CustomerBillingAddresses"));

// Now BillingAddress columns are in a separate table
// with a FK back to CustomerId

OwnsMany pour les collections d'objets valeur

OwnsMany configure une collection d'entités détenues, par exemple une liste d'adresses e-mail ou de numéros de téléphone stockée dans une table enfant.

public class Customer
{
    public int Id { get; set; }
    public IList<PhoneNumber> PhoneNumbers { get; set; } = new List<PhoneNumber>();
}

public class PhoneNumber
{
    public string Number { get; set; } = "";
    public string Type   { get; set; } = "";
}

modelBuilder.Entity<Customer>()
    .OwnsMany(c => c.PhoneNumbers,
        phone => phone.ToTable("CustomerPhones"));

Table par hiérarchie ou entités détenues

Les filtres globaux fonctionnent également bien avec l'héritage TPH (table par hiérarchie), dans lequel une colonne discriminante distingue les sous-types. Appliquez un filtre basé sur le discriminant pour les requêtes propres à un type.

// Base entity
public abstract class Payment { public int Id { get; set; } }
public class CardPayment  : Payment { public string CardLast4 { get; set; } = ""; }
public class BankPayment  : Payment { public string IBAN { get; set; } = ""; }

// All Payment subtypes in one table with a Discriminator column
// No extra config needed — EF Core handles TPH by default
var cards = await _db.Set<CardPayment>().ToListAsync();
// SQL: SELECT * FROM Payments WHERE Discriminator = 'CardPayment'

Cas concret : isolation des locataires dans un SaaS

Une configuration complète de gestion de plusieurs locataires dans un SaaS combine des filtres de requêtes globaux avec un contexte de locataire associé à une portée, afin que chaque requête soit automatiquement limitée sans code propre à chaque requête.

// Scoped tenant service
public class TenantContext
{
    public Guid TenantId { get; set; }
}

// DbContext uses it
modelBuilder.Entity<Invoice>().HasQueryFilter(
    inv => inv.TenantId == _tenantCtx.TenantId);
modelBuilder.Entity<Customer>().HasQueryFilter(
    c => c.TenantId == _tenantCtx.TenantId);

// Queries are automatically scoped:
var invoices = await _db.Invoices.ToListAsync();
// WHERE TenantId = 'current-tenant-guid'

Vérification rapide

Quelle méthode appelez-vous pour contourner temporairement un filtre de requête global dans une requête donnée ?

Récapitulatif : filtres globaux et entités détenues

Points essentiels :

  • Les filtres de requêtes globaux ajoutent automatiquement des prédicats WHERE à chaque requête d'une entité
  • Ils sont idéaux pour la suppression logique, la gestion de plusieurs locataires et la sécurité au niveau des lignes
  • Contournez-les avec IgnoreQueryFilters() pour les opérations d'administration ou de restauration
  • Les entités détenues modélisent les objets valeur : aucune PK, stockage dans la table du propriétaire
  • OwnsOne / OwnsMany configurent les relations détenues ; utilisez ToTable() pour les séparer
Gratuit pour commencer

Apprends C# avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
93
Leçons
346

Questions Fréquemment Posées

La leçon « Filtres de requêtes globaux et entités possédées » est-elle gratuite ?

Oui — le texte complet de « Filtres de requêtes globaux et entités possédées » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours C# Academy, passe à CoddyKit PRO. Le cours C# Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Filtres de requêtes globaux et entités possédées » ?

Appliquez l’isolation des locataires et la suppression logique avec des filtres de requêtes globaux, puis mappez des objets-valeurs avec des types d’entités possédées. Tu pratiques C# Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer C# Academy ?

Aucune expérience préalable n'est requise. C# Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.

Combien de temps prend la leçon « Filtres de requêtes globaux et entités possédées » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon C# Academy ?

Oui. Chaque leçon C# Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Chargement immédiat, différé et explicite
  2. SQL brut, procédures stockées et interpolation
  3. Requêtes compilées et performances
  4. Filtres de requêtes globaux et entités possédées
← Retour à C# Academy