dimanche 17 février 2008

Techdays 2008 : Linq avancée

Linq avancée
Une session présentée par Mitsuru furuta

Une session pas simple sur LinqToObject avec des méthodes à la volée, de la métaprogrammation, de l'extension de méthodes et un peu d'architecture.

Méthodes à la volée

L'idée de cette présentation est de créer des méthodes à la volée et de les exécuter.

On commence simplement par une expression lambda sur un délégué multipliant un entier par 2. L'expression lambda donne un arbre d'expression que l'on compile en du code exécutable. Il suffit ensuite d'appeler la méthode avec un paramètre:

Expression<func<int,int>> MultiplyBy2 = (paramToMultiply) => 2 * paramToMultiply;
var FuncMultiplyBy2 = MultiplyBy2.Compile();
string result1 = FuncMultiplyBy2(12).ToString(); ///return 24

On peut écrire cette même expression directement en expression lambda, sans passer par un délégué:


var param = Expression.Parameter(typeof(int),"param1");
Expression mult = Expression.Multiply(
param,
Expression.Constant(2, typeof(int)));
var lambda = Expression.Lambda<func<int,int>>(mult, param);
var f = (Func<int,int>)lambda.Compile();
string resutl = f(12).ToString(); // return 24

IEnumerable/IQueryable

Quelle différence fondamentale entre IEnumarable et IQueryable?
Ils servent à délimiter ce qui est du code ou de l'expression.

IQueryable créé de l'expression plutôt que du code. Cette expression est analysable et modifiable (interception, factoring de code) et peut aussi être évaluée. On peut l'appeler avec

.AsQueryable()

IEnumerable est un reader. Il renvoit donc un curseur sur les enregistrements. On peut l'appeler par

.AsEnumerable()

On verra une conséquence de cela dans la partie Architecture.

MetaProgrammation

Pour faire de la métaprogrammation, on peut s'en sortir avec de la réflexion, ce qui n'est pas toujours conseillé car cela ajoute un overhead.
L'idée est ici de passer par une expression et d'intercepter la navigation dans l'arbre d'expression (lire l'article Implémenter un visiteur de l'arborescence de l'expression) afin de remplacer dans l'arbre le bout de code qui nous intéresse en surchargeant ExpressionVisitor (lire l'article modifier une arborescence d'expression).

Les expressions permettent donc une première approche de la métaprogrammation (et donc un premier niveau de dynamicité) mais qui a ses limites (pas de créations d'instances,...).

Tri de DataGrid dynamique

Trier des DataGrid sur des colonnes que l'on ne connait pas a priori (au niveau objet) mais dont on a le nom (chaine de caractère via le ColumnHeaderMouseClick) n'est pas simple avec du databinding sur des providers Linq. En effet, il faut ajouter à la requête un orderby sur une propriété:

var myProcess = from a in Process.GetProcesses()
orderby a.ProcessName descending
select new { a.ProcessName, a.ProcessorAffinity };

Une première manière de faire consiste à récupérer la propriété par reflexion puis en utilisant un MemberExpression:

Type process = typeof(Process);
PropertyInfo prop = process.GetProperty("ProcessName");
Expression exp = Expression.Parameter(process,"Process");
MemberExpression memb = Expression.Property(exp, prop);
var myProcess = from a in Process.GetProcesses()
orderby memb descending
select new { a.ProcessName, a.ProcessorAffinity };

Ou sinon on peut étendre l'API avec des méthodes d'expressions basées sur des chaines de caractères avec Dynamic Query Library.

Architecture

Question: Dans la couche métier, faut-il renvoyer des List<> ou des IEnumerable?
IEnumerable, on l'a vue, est un reader. Il ne créé donc pas de collection. On renvoyant à chaque couche des List<>, on créé autant de collections intermédiaires qui n'ont pas de plus value si ce n'est faire office de passe-plat.
En exposant un IEnumarable, on passe un curseur au travers des couches ce qui produit un gain en consommation mémoire par rapport aux collections. La limitation est le manque d'accès aux propriétés par index xxx.Prop[i] mais la plupart du temps on utilise les boucle while ou foreach.

IQueryable/IEnumarable et enjeux de sécurité:
1er cas:

UIMétierData
Expose IQueryable
Var q = from x in table...
from c in table
where c.StartWith()
=> la requête est modifiée par une demande
de la couche
UI

Dans ce cas simple, on n'a pas d'isolation entre les couches


2ème cas

UIMétierData
Expose IEnumerable
Var q = from x in table...
from c in table
where c.StartWith()
=> filtre des réponses dans la couche métier=> la requete n'est pas modifiée
from c in (IQueryable)table
where c.StartWith()
=> la requete est modifiée par une demande
de la couche UI

Un Cast côté UI permet de modifier la requête directement dans la couche Data. On a toujours un problème de sécurité entre couches.

3ème Cas

UIMétierData
Expose IEnumerable
Var q = from x in table.AsEnumerable()...
from c in table
where c.StartWith()
=> filtre des réponses dans la couche métier=> la requête n'est pas modifiée

Cette fois en exposant IEnumerable avec .AsEnumerable() l'expression ne peut pas être castée en IQueryable. On gagne donc en performance sans perdre en sécurité.

Interception de requête

Le IQueryable étant une expression, on peut l'intercepter afin de composer une nouvelle requête on ajoutant simplement une clause where par exemple dans un but de factorisation de code.

On peut trouver un article complet sur linq ici par Thomas Lebrun.

Aucun commentaire:


Laurent Morisseau, auteur de ce blog, pour me contacter