Créer des requêtes C# Linq dynamique avec Predicate Builder

Introduction

La fonctionnalité Linq (Language Integrated Query, que l’on pronnonce « link ») est l’une des nouveautés majeurs apparues avec le Framework 3.5 en 2007. Elle est aujourd’hui beaucoup utilisée dans les développements .Net. Si vous êtes développeurs .Net, vous devez donc souvent être confrontés par ce type de bout de code.

var dataSource = employees.Where(employee => employee.IsActive);

Une simple requête Where() qui filtre les employés actifs pour remplir une grille dans une vue, peu importe le type de vue. Cependant, un change du business qui, par exemple, souhaiterait avoir la possibilité d’afficher ou non, tous les employés ou juste les employés actifs à l’aide d’une case à cocher « Employés actifs ». La requête deviendrait donc la suivante.

var dataSource = employees.Where(employee => !employeesFilters.Contains(EmployeeFilterType.IsActive) || employee.IsActive);

La requête reste ici assez simple, vu qu’il n’y a qu’un seul critère. Cependant, si le business continue à ajouter de plus en plus de change, cette dernière pourrait devenir de plus en plus difficile à lire et à maintenir.

Predicate vs Func<t> vs Action

La méthode Linq Where() reçoit en paramètre un delegate de type Predicate. Il existe plusieurs type de delegate, les 3 les plus utilisés sont :

Func : Beaucoup utilisé en Linq, il est utilisé pour transformer l’argument en une structure complexe, où tout simplement en une propriété.

// Transform From IEnumarable<Employee> to : 
employees.Select(item => item.IsDeveloper); // Type : IEnumerable<bool>
employees.Select(item => new { item.IsDeveloper, item.IsOnHoliday }); // Type : IEnumrable<*Complex type*>

Predicate : Il s’agit d’une forme de Func, mais qui retourne systématiquement un boolean. Dans le cadre de notre méthode Where() : il retournera tous les éléments satisfaisant la condition.

var dataSource = employees.Where(employee => employee.IsActive);

Action : Il s’agit d’un delegate qui fait une action sur l’argument donné, mais qui ne retourne rien.

employees.ForEach(item => { DoSomething(item); });
// or the same line but using method group
employees.ForEach(DoSomething);

Predicate Builder

Voyons à présent comment simplifier la lecture et par conséquent, la maintenance d’une requête Linq complexe. Pour ce faire, je vais vous présenter le concept de PredicateBuilder, conçu par Josepj Albahari, version améliorée ensuite par Monty’s Gush.

Créer un predicate builder

Avant de créer un predicate, ajoutons des données dans notre liste d’employés. Ces données permettront d’analyser le résultat de chaque requête créée.

employees.Add(new Employee() { FullName = "Damien VDK", IsActive = true, IsOnHoliday = false, IsDeveloper = true });
employees.Add(new Employee() { FullName = "Donald Trump", IsActive = true, IsOnHoliday = true, IsDeveloper = false });
employees.Add(new Employee() { FullName = "Rosy Zoé", IsActive = true, IsOnHoliday = false, IsDeveloper = false });
employees.Add(new Employee() { FullName = "John Presper Eckert", IsActive = false, IsOnHoliday = false, IsDeveloper = false });

Il y a 3 manières de créer un PredicateBuilder :

  • PredicateBuilder.True<T>()
  • PredicateBuilder.False<T>()
  • PredicateBuilder.Create<T>(predicate)

PredicateBuilder.True<T>()

Cette méthode correspond à l’expression suivante : Where(item => true). Si nous appliquons ce predicate sur nos listes d’employés, il retournera l’ensemble des employés. L’application d’un predicate créé via PredicateBuilder est expliquée plus tard dans cet article.

PredicateBuilder.False<T>()

Vous l’aurez sans doute deviné, cette méthode correspond à l’expression suivante : Where(item => false). Elle, accompagné d’un predicate de type « or », permet de restreindre à quelques critères spécifiques, puisqu’elle retourne, lorsqu’elle est créée seule, aucun élément.

PredicateBuilder.Create<T>(Predicate)

Cette méthode créée un instance de type PredicateBuilder en prenant en paramètre un premier predicate. Elle correspond donc à l’expression suivante : Where(predicate). Exemple : PredicateBuilder.Create(item => item.IsActive); ne retournera que les employés dont la valeur de la propriété IsActive est à True.

« And » and « Or » (Or « And » or « Or »)

Maintenant que l’instance de type PredicateBuilder est créée, on va voir comment on peut ajouter des conditions. Pour cela, deux méthodes possibles : And(predicate) et Or(predicate).

var predicate = PredicateBuilder.Create<Employee>(item => item.IsActive);
if (showInactiveChecked) predicate = predicate.Or(item => !item.IsActive);

Dans l’exemple ci-dessus, la première ligne va créer un predicate qui retournera tous les employés actifs, et la deuxième ligne ajoutera, si la valeur de showInactiveChecked est à true, également les employers inactifs. Il correspond donc à la ligne suivante :

employees.Where(item => item.IsActive || (showInactiveChecked && !item.IsActive));

De la même manière, il est possible d’utiliser un And au lieur d’un Or.

var predicate = PredicateBuilder.Create<Employee>(item => item.IsActive);
if (showOnHolidayOnly) predicate = predicate.And(item => item.IsOnHoliday);

Ce bout de code correspond à la requête Linq suivante :

employees.Where(item => item.IsActive && item.IsOnHoliday));

Utilisation du PredicateBuilder

À présent que nous avons créé notre PredicateBuilder, il ne reste plus qu’a l’appliquer sur une collection. Pour ce faire c’est très simple, il suffit de passer notre instance de PredicateBuilder notre méthode Linq Where().

// If you are getting data from EF, you don't need to call the Copmpile() method
// databaseContext.Employees.Where(predicate) will works
employees.Where(predicate.Compile()).ToList().ForEach((item) => { Console.WriteLine(item.FullName); });

Il ne reste qu’a convertir vos requêtes Linq complexes pour les rendre plus facile à lire. N’hésitez pas à donner votre avis ou poser vos questions mais aussi de partager l’article :).

You may also like...

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *