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:
UI Métier Data 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
UI Métier Data 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
UI Métier Data 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:
Enregistrer un commentaire