EF Core - Créer un filtre global sur l'ensemble de vos requêtes.

fborgies

François Borgies

Posted on November 6, 2020

EF Core - Créer un filtre global sur l'ensemble de vos requêtes.

Dans cet article je vous explique comment définir et configurer, à travers l'exemple de la suppression logique, un filtre sur l'ensemble de vos requêtes Linq.

Avant de rentrer dans le vif du sujet, je vous mets en contexte. Il y a quelques jours, j'ai élaboré un modèle de données qui devait prendre en compte une suppression logique de l'ensemble des entités de ce dernier. J'ai donc naturellement prévu dans le Core de mon application, une classe de base dont héritent directement, ou par transition, les différentes entités de mon modèle :

public abstract class BaseEntity : ISoftDelete
{
    // Implémente ISoftDelete   
    public bool Deleted { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Une fois mon Core défini et mes différents Mappings EfCore créés, je m'attelle à la réalisation des Repositories et des opérations de CRUD. Mais, une chose me saute aux yeux. Pour chaque requête Linq, je me refuse à écrire ce genre de ligne redondante :

public Blog Get(Guid blogId)
{
    return _ctx.Blogs.FirstOrDefault(b => b.Id.Equals(blogId) && !b.Deleted);
}
Enter fullscreen mode Exit fullscreen mode

Evidemment il est possible de créer un filtre sur l'entité dans la méthode OnModelCreating de votre DbContext :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().HasQueryFilter(p => !p.Deleted);
}
Enter fullscreen mode Exit fullscreen mode

C'est une solution viable mais étant donné que je devais l'appliquer à l'ensemble de mes entités, je ne souhaitais pas configurer chaque entité de cette façon. Comment faire alors pour appliquer ce filtre une seule fois et pour l'ensemble des requêtes qui manipulent des entités de mon modèle ?
La réponse est via la réflexion. Je ne vais pas rentrer dans le détail de ce mécanisme très puissant du .Net dans cet article.
Là n'est pas le but. J'invoque donc une méthode de filtre au moment de l'exécution d'une requête sur mon modèle de données. Pour cela j'ai besoin d'une classe d'extension :

internal static class SoftDeleteQueryExtension
{
    internal static void AddSoftDeleteQueryFilter(this IMutableEntityType entityData)
    {
        var methodToCall = typeof(SoftDeleteQueryExtension)
            .GetMethod(nameof(GetSoftDeleteFilter), BindingFlags.NonPublic | BindingFlags.Static)
            .MakeGenericMethod(entityData.ClrType);

        var filter = methodToCall.Invoke(null, new object[] { });

            entityData.SetQueryFilter((LambdaExpression)filter);
     }

     private static LambdaExpression GetSoftDeleteFilter<TEntity>() where TEntity : class, ISoftDelete
     {
         Expression<Func<TEntity, bool>> filter = x => !x.Deleted;
            return filter;
     }
Enter fullscreen mode Exit fullscreen mode

Cette classe appliquera le filtre GetSoftDeleteFilter, chaque fois qu'une requête sera utilisée sur une entité de mon modèle.

Il ne reste plus qu'à configurer mes entités pour bénéficier automatiquement de ce filtre, dans le OnModelCreating :

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
 foreach (var entityType in modelBuilder.Model.GetEntityTypes())
 {      
  if(typeof(ISoftDelete).IsAssignableFrom(entityType.ClrType))
  {
   entityType.AddSoftDeleteQueryFilter();
  }
 }
}
Enter fullscreen mode Exit fullscreen mode

Le code boucle sur chaque entité de mon model, si celle-ci implémente l'interface ISoftDelete, on lui ajoute le filtre.

Votre filtre est prêt ! L'exemple du départ devient donc :

public Blog Get(Guid blogId)
{
    return _ctx.Blogs.FirstOrDefault(b => b.Id.Equals(blogId));
}
Enter fullscreen mode Exit fullscreen mode

Cette requête filtre automatiquement les blogs qui sont supprimés logiquement. En effet, ma classe Blog hérite de BaseEntity qui implémente l'interface ISoftDelete et donc, par transition, Blog implémente également cette interface. Par conséquent au moment où la requête sera exécutée, le filtre sera appelé et appliqué.

En bonus, si vous souhaitez récupérer dans certaines requêtes les entités supprimées logiquement, vous devez utiliser la méthode IgnoreQueryFilters() sur votre requête :

var deletedEntities= _ctx.Blogs.IgnoreQueryFilters()
    .Where(x => x.Deleted)
    .ToList();
Enter fullscreen mode Exit fullscreen mode

Happy Coding !

💖 💪 🙅 🚩
fborgies
François Borgies

Posted on November 6, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related