Dans le cas où votre système a été mis en place avant l'arrivée de Linq, la couche d'accès ne renvoie pas d'« IQueryable<T> » mais plus vraisemblablement des IEnumerable<T>. Si vous souhaitez intégrer pour vos utilisateurs une possibilité de calcul dynamique sans à avoir à retoucher votre couche d'accès aux données et encore moins votre couche métier, il vous faudra développer un parseur. Ce dernier devra intégrer inévitablement de la génération de code donc de l'IL (intermediate language). Outre la gestion des éléments de la pile de chaque méthode en IL que vous développerez, il vous faudra aussi gérer une pile d'expression en fonction de la formule de calcul de l'utilisateur, identifier les propriétés de type valeur de votre objet, gérer le type decimal qui demande un traitement particulier. Bref, ce n'est pas infaisable mais ça risque de vous demander pas mal de temps pour mettre au point ce parseur.
Il serait dommage de ne pas utiliser le parseur fourni en exemple avec Visual Studio 2008 présenté ci-dessus. Seulement ce dernier a été écrit pour prendre en charge les « IQueryable » et non pas les IEnumerable<T>. Il y a sans doute un moyen de détourner le code pour que celui-ci puisse prendre en charge ces « IEnumerable<T> ».
En fait, le principe est simple : pour pouvoir utiliser le requêteur dynamique fourni en exemple dans Visual Studio 2008, il faut transformer en entrée le IEnumerable<T> en IQueryable<T>.
Prenons un exemple. Imaginons que nous ayons une liste de contrats (Police) que vos commerciaux ont vendus. Chaque client s'engage à verser un montant (Versement) suivant une périodicité définie (Periodicite 12=mensuel, 4=trimestriel). Sur ces contrats, vous percevez de la part de la compagnie qui vous demande de les vendre, un pourcentage (Taux). Vous souhaitez rémunérer vos commerciaux en fonction de leur vente par un calcul (Volume). Vous pouvez définir une entité Contrat qui peut ressembler à ceci :
public class ContratENT
{
public int Periodicite {get; set;}
public decimal Versement {get; set;}
public decimal Taux {get; set;}
public string Police { get; set; }
}
Nous pouvons simuler une initialisation pour notre exemple à défaut d'avoir de vrais données :
//Initialisation simulée d'un ensemble de 8000 contrats
List<ContratENT> c = new List<ContratENT>();
int cpt = 0;
do
{
tabSource[cpt] = cpt1;
c.Add(new ContratENT { Police = "num" + cpt.ToString(), Taux = 5.5M, Periodicite = 12, Versement = cpt * 10 });
}
while (cpt++ < 7999);
A partir des données, nous pouvons bénéficier du requêteur dynamique à condition de fournir un objet implémentant « IQueryable ». « List<ContratENT> » n'implémente pas cette interface. La méthode d'extension « AsQueryable() » fournie par Linq To Objects peut venir à notre secours :
int montantMin = 100;
var req = c.AsQueryable()
.Where("Versement >= @0", montantMin)
.OrderBy("Versement desc")
.Select("new(police, Taux, Periodicite,Versement, (Versement * Periodicite) * (Taux / 10) as Volume )");
Ce code fonctionne très bien. Par contre, il impose à chacun de vos développeurs de connaître cette astuce. De plus, si vous souhaitez effectuer un « ToList() », il vous faudra caster pour passer d'un « IQueryable » à un « IQueryable<object> » ou plus généralement à un « IEnumerable<object> » puisque « DynamicClassN » (la classe générée par le « new » dans le « select ») n'est pas disponible puisque générée lors de l'exécution.
dataGridView1.DataSource = req.Cast<object>().ToList();
Si on souhaite fournir la prise en charge intégrale sous forme de méthodes d'extension, on ajoutera les méthodes suivantes à la classe « DynamicQueryable » :
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> source, string ordering, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (ordering == null) throw new ArgumentNullException("ordering");
return OrderBy(source.AsQueryable(), ordering, values).AsEnumerable();
}
public static IEnumerable<T> Where<T>(this IEnumerable<T> source, string predicate, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (predicate == null) throw new ArgumentNullException("predicate");
return Where(source.AsQueryable(), predicate, values).AsEnumerable();
}
public static IEnumerable<Object> Select<T>(this IEnumerable<T> source, string selector, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
return Select((IQueryable)source.AsQueryable(), selector, values).Cast<Object>();
}
Il ne sera plus nécessaire alors de faire appel au préalable à la méthode d'extension « AsQueryable() » et de caster par « Cast<object>() ».
J'espère que cette astuce vous invitera à découvrir les exemples fournis avec Visual Studio 2008, vous permettra d'effectuer des requêtes dynamiques sur vos listes d'objets et vous épargnera le développement d'un parseur d'expressions.
Je remercie Matthieu Mezil, Thomas Lebrun, Fabrice Romelard, Laurent Kempé pour la relecture de cet article.