Olivier Dahan

Microsoft MVP Silverlight 2013, 2012,
2011, MVP CAD 2010, MVP C# 2009


Membre du Developer Guidance Advisory Council Microsoft

Audit, Conseil, Formation, Développement
[WPF, Silverlight, WinRT, MonoDroid]

Historique

Donner du peps à Linq ! (Linq dynamique et parser d'expressions)

Read this article in your language IT | EN | DE | ES

J'ai eu moulte fois ici l'occasion de dire tout le bien que  je pense de LINQ, de sa puissance, de sa souplesse, de sa versatilité (de xml à SQL en passant par les objets et l'Entity Framework).

Mais une petite chose me chagrinait : toute cette belle puissance s'entendait "hard coded". Je veux dire par là que les expressions et requêtes LINQ s'écrivent en C# dans le code et qu'il semblait très difficile de rendre la chose très dynamique, sous entendu dépendant d'expressions tapées à l'exécution de l'application. En un mot, faire du LINQ dynamique comme on fait du SQL dynamique.

Ce besoin est bien réel et ne concerne pas que les sources SQL. Prenez une simple liste d'objets que l'utilisateur peut vouloir trier selon l'une des propriétés ou filtrer selon le contenu des objets... Comment implémenter une telle feature ?

Contourner ce manque en écrivant des parsers, en jonglant avec les expressions lamba et la réflexion n'est pas, il faut l'avouer, ce qu'il y a de plus simple. Mais rassurez-vous chez Microsoft des gens y ont pensé pour nous ! Seulement voila, la chose est assez confidentielle il faut bien le dire, et on ne tombe pas dessus par hasard. Ou alors c'était un jour à jouer au Lotto, voire à contrôler, selon les mauvaises langues, l'emploi du temps de sa femme !

La chose s'appelle "LINQ Dynamic Query Library", un grand nom pour une petite chose puisqu'il s'agit en réalité d'un simple fichier source C# à ajouter à ses applications.  Mais quel fichier source !

Toute la difficulté consiste donc à savoir que ce fichier existe et mieux, où il se cache... C'est là que c'est un peu vicieux, la chose se cache dans un sous-répertoire d'un zip d'exemples Linq/C#, fichier lui-même astucieusement planqué dans la jungle des milliers de téléchargement de MSDN...

Sans plus attendre allez chercher le fichier cliquant ici.

Une fois que vous aurez accepté les termes de la licence ("I Accept" tout en bas à gauche) vous recevrez le fichier "CSharpSamples.zip". Dans ce dernier (dont tout le contenu est vraiment intéressant) aller dans le répertoire "LinqSamples" puis dans le projet "DynamicQuery" vous descendrez dans une autre sous-répertoire appelé lui aussi "DynamicQuery" (non, ce n'est pas un jeu de rôle, juste un téléchargement MSDN!). Et là, le Graal s'y trouve, "Dynamic.cs".

Copiez-le dans vos projets, et ajouter un "using System.Linq.Dynamic". A partir de là vous pourrez utiliser la nouvelle syntaxe permettant de faire du LINQ dynamique. A noter que dans le répertoire de la solution vous trouverez aussi un fichier "Dynamic Expressions.html" qui explique la syntaxe du parser.

Quelles sont les possibilités de LINQ Dynamic Query Library ?

C'est assez simple, pour vous expliquer en deux images je reprend deux captures écrans tirées du blog de Scott Guthrie (un excellent blog à visiter si vous parlez l'anglais).

Voici un exemple de requête LINQ (en VB.NET, si j'avais refait la capture cela aurait été du C#, mais je ne vais pas rouspéter hein ! )

Maintenant voici la même requête utilisant du LINQ dynamique :

Vous voyez l'astuce ? Le filtrage et le tri sont maintenant passés en chaîne de caractères... Bien entendu cette chaîne peut avoir été construite dynamiquement par code (c'est tout l'intérêt) ou bien saisie depuis un TextBox rempli par l'utilisateur.

Ici pas de risque d'attaque par "SQL injection", d'abord parce que LINQ n'est pas limité à SQL et que le problème ne se pose que dans ce cas, mais surtout parce que LINQ to SQL utilise des classes issues d'un modèle de données "type safe" et que par défaut LINQ to SQL est protégé contre les attaques de ce type. Pas de souci à se faire donc.

Pour terminer j'insisterai lourdement sur le fait que LINQ ne se limite pas aux données SQL, et que l'astuce de LINQ dynamique ici décrite s'applique même à LINQ to Object par exemple. Laisser la possibilité à un utilisateur de trier une List<> ou une Collection<>, de la filtrer, tout cela en mémoire et sans qu'il y ait la moindre base de données est bien entendu possible.

LINQ est vraiment une innovation majeure en matière de programmation, pouvoir le dynamiser simplement c'est atteindre le nirvana...

Je vous laisse vous jeter sur vos claviers pour expérimenter du LINQ dynamique, amusez-vous bien !

..et surtout.. Stay tuned !

 

Comments (8) -

Sylvain
Sylvain
6/15/2008 11:47:25 PM

Juste une petite question de newbie mais ... :
Qu'est ce qui empêche de créer dynamiquement la première requête LINQ ?
(genre à la place de Where p.CategoryID = 2 ou pourrait placer Where p.CategoryID = aCategory (aCategory étant une variable int))

A moins que ton post ne concerne uniquement que le remplacement dynamique de toute la clause Where...

Olivier
Olivier
6/16/2008 1:31:22 PM

Tu as donné la réponse toi-même Smile
En effet tu peux écrire une requête LINQ dans laquelle le Where ou le OrderBy peuvent dépendre du contenu d'une variable, mais cela s'entend dans une logique "hard coded", la clause Where existe et prévoit de tester telle ou telle propriété. La variable ne peut contenir qu'une valeur, le test existe toujours lui et ne peut être modifié.
L'intérêt de LINQ Dynamic Query Library est justement de fournir un parser d'expression pour remplacer la totalité de la clause Where, OrderBy ou Select par des chaines de caractères. Le parser autorise d'ailleurs une syntaxe un peu différente de C#, ce n'est pas case sensitive par exemple.
C'est aussi très intéressant pour créer des classes anonymes entièrement par code puisque dans le Select on peut faire un New avec la liste des propriétés qu'on veut.
Cela donne donc encore plus de souplesse à LINQ.

Mais un dernier mot sur les variables dans les requêtes LINQ, ce que tu évoques. Un exemple pour comprendre:
int entier = 22; List<int> MaListe = ... // une liste d'entiers de 1 à 50
var query = from i in MaListe where i>entier select i;
entier = 50;
foreach (int x in query) Console.WriteLine(x);

Qu'est ce qui apparaîtra sur la console ? La liste des entiers de 23 à 50 ou bien rien du tout ?
Rien du tout...
En effet, la requête LINQ est toujours exécutée lorsque son résultat est énuméré et non quand la requête est "fabriquée".
De fait, on le voit dans cet exemple, utiliser des variables dans une requête LINQ peut réserver de grosses surprises si le contenu de ces variables est modifié par le code entre le moment de la définition de la requête et son énumération... A utiliser avec beaucoup de prudence donc !

Sylvain
Sylvain
6/16/2008 3:57:39 PM

Merci pour ces infos vraiment utiles !
Je crois que dans toutes mes applis actuellement en prod, j'ai des req SQL qui comportent des parties Select, Where, Group by, Order By dynamiques !

Quant à ta remarque sur l'énumération qui déclenche l'exécution de la requête, merci maître Wink

Olivier
Olivier
6/16/2008 9:29:09 PM

Smile
Je fouine, je fouine tellement.. quand ça peut aussi servir aux autres, et surtout aux amis alors tant mieux !

Bruno
Bruno
6/22/2008 2:25:53 AM

J'avais déjà entendu parler de LINQ, sans avoir été convaincu plus que cela, pour me pencher dessus.
Mais alors là, je ne peux plus y échapper.

Pour reprendre les mots de Sylvain ; Merci maître.

Olivier
Olivier
6/22/2008 7:46:18 AM

Je suis heureux que ce billet puisse te redonner envie de regarder Linq de plus près, tu verras, on ne peut plus s'en passer...

Hakan
Hakan
10/29/2008 9:12:21 AM

Juste une ptite question :
Pourquoi ca ne fonctionne pas avec les Collection<T> ?
J'ai l'erreur suivante :
cannot convert from 'System.Collections.ObjectModel.Collection<MonObjet>' to 'System.Linq.IQueryable'

Merci d'avance.

Olivier
Olivier
10/29/2008 11:24:45 AM

Une Collection<T> est un IEnumerable<T> mais pas un IQueryable<T>, les deux premières ne sont donc pas compatibles directement avec la dernière. En revanche en interrogeant une Collection<T> tu peux obtenir un IQueryable<T>.
Par exemple: var q = from element in maCollection select element;
q sera un IQueryable<T> contenant tous les éléments de la collection maCollection<T>.
Mais il est en réalité très difficile de te répondre car sans le code qui génère l'erreur dont tu parles, il me manque trop d'éléments pour comprendre ce qui se passe.

Add comment

  Country flag

biuquote
  • Comment
  • Preview
Loading