lu 1973 fois
1 pages
Frédéric Mélantois
Requêtes dynamiques sur les IEnumerable<T>
A partir d'un exemple fourni avec Visual Studio 2008, initialement prévu pour tout objet Queryable<T>, nous allons présenter comment en ajoutant très peu de code rendre disponible aux IEnumerable<T> un requêteur dynamique.
Par Frédéric Mélantois publié le 24/04/2008 à 15:03
 
Si vous avez installé Visual Studio 2008, vous pouvez consulter les nombreux exemples fournis avec cet outil de développement. Ils sont très riches pour appréhender les nouveautés de cette version.
 
/content/422cd3ca-1f76-40b2-a664-8bad94d1f129/image1.JPG
 
L'exemple que nous allons exploiter se trouve dans l'archive CSharpSamples.zip.
 
/content/422cd3ca-1f76-40b2-a664-8bad94d1f129/image2.JPG
 
La localisation exacte de la solution est précisée par les copies d'écran ci-dessus. Ce qui nous intéresse pour cet article se trouve dans le fichier « Dynamic.cs ». Mais avant de décortiquer ce fichier, examinons l'exemple présenté dans « program.cs ». A partir d'une base de données d'exemple : « Northwind » mappée pour fonctionner avec Linq To Sql, un mode de requête inhabituel est utilisé. Celui-ci intègre des données dynamiques :

string dbPath = Path.GetFullPath(Path.Combine(Application.StartupPath, @"..\..\..\..\Data\NORTHWND.MDF"));

string sqlServerInstance = @".\SQLEXPRESS";

string connString = "AttachDBFileName='" +

    dbPath + "';Server='" +

    sqlServerInstance + "';user instance=true;Integrated Security=SSPI;Connection Timeout=60";

 

Northwind db = new Northwind(connString);

db.Log = Console.Out;

 

var query =

    db.Customers.Where("City == @0 and Orders.Count >= @1", "London", 10).

    OrderBy("CompanyName").

    Select("New(CompanyName as Name, Phone)");

 

Console.WriteLine(query);

Console.ReadLine();

Une recherche rapide nous montre que des méthodes d'extension sont utilisées. L'utilisation du namespace « System.Linq.Dynamic » nous précise la location de ces dernières. Elles se trouvent dans le fichier « Dynamic.cs » en particulier dans la classe « DynamicQueryable » donc voici les signatures des différentes méthodes :
 
/content/422cd3ca-1f76-40b2-a664-8bad94d1f129/image3.JPG
 
Si nous prenons par exemple la méthode d'extension « Where », nous pouvons facilement analyser le mécanisme en regardant le code :

public static IQueryable Where(this IQueryable source, string predicate, params object[] values) {

    if (source == null) throw new ArgumentNullException("source");

    if (predicate == null) throw new ArgumentNullException("predicate");

    LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(bool), predicate, values);

    return source.Provider.CreateQuery(

        Expression.Call(

            typeof(Queryable), "Where",

            new Type[] { source.ElementType },

            source.Expression, Expression.Quote(lambda)));

}

La chaîne « predicate » ainsi que les éventuelles valeurs de paramètres « values » permettent de fabriquer une expression lambda qui sera pris en charge par la mécanique de Linq. Une classe statique « DynamicExpression » sert de boîte à outil :
 
/content/422cd3ca-1f76-40b2-a664-8bad94d1f129/image4.JPG
 
Voici le détail de la méthode « ParseLambda » :

public static LambdaExpression ParseLambda(ParameterExpression[] parameters, Type resultType,

                                            string expression, params object[] values)

{

    ExpressionParser parser = new ExpressionParser(parameters, expression, values);

    return Expression.Lambda(parser.Parse(resultType), parameters);

}

Nous voyons très facilement qu'une classe interne se charge de parser la chaîne d'expression et prend en charge les valeurs dynamiques fournies. Nous ne détaillerons pas le contenu de la classe « DynamicParser » mais je vous recommande de l'étudier car elle est très riche en enseignement.
Grâce à l'exemple fourni, nous pouvons constater que la classe « DynamicQueryable » regroupant toutes les méthodes d'extension permettant un requêtage dynamique est très pratique pour tous les objets implémentant l'interface « IQueryable ». Mais malheureusement, elle ne prend pas en charge les objets implémentant uniquement « IEnumerable<T> ».
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.
Matthieu Mezil, un de nos spécialistes de Linq to Entities, MVP C# et auteur de Tech Head Brothers, a fait mention de possibles requêtes sous forme de chaîne de caractères. Je vous invite vivement à consulter ce post : Pourquoi ESQL, c'est bien

 Commentaire - Requêtes dynamiques sur les IEnumerable<T> 

 Dernières Publications      

Windows Media Center et WCF : développez votre maison intelligente
  Le développement d'applications pour Windows Media Center est facilité avec l'arrivée du SDK 5.3. Même si l'on sent un modèle objet bien lourd derrière, il devient plus facile d'exposer les fonctionnalités de WMC sous la forme de services WCF.
par Frédéric Colin posté le 23/06/2008 à 08:04, lu 295 fois, #0
Notions avancées avec Biztalk Server 2006 R2
  Utilisation des notions d'interchange, corrélation et convoi avec BizTalk Server 2006 R2
par Kader Yildirim posté le 09/06/2008 à 08:04, lu 288 fois, #0
Lucene Persistence Engine pour Evaluant Universal Storage Services
  Suite à l'article de Laurent Kempé, voici un moteur de stockage pour EUSS permettant l'indexation d'entités métier avec Lucene.
par Nicolas Penin posté le 01/06/2008 à 23:38, lu 510 fois, #1
Tags: C#, Linq
XMLA Trivia : Découverte du XMLA
  Le XMLA (XML for Analysis) est un langage normalisé par plusieurs éditeurs BI pour simplifier l'accès aux données aux cubes et aux métadonnées des bases multidimensionnelles.
par Renaud Harduin posté le 25/05/2008 à 11:57, lu 523 fois, #0
Exploiter les données CSV via Linq en toute simplicité
  A partir du requêteur dynamique fourni en exemple avec Visual Studio 2008, nous allons essayer de remplir les propriétés d'un ensemble d'objets à partir des données d'un fichier CSV. Nous enrichirons aussi le parseur de nos propres fonctions.
par Frédéric Mélantois posté le 17/05/2008 à 11:41, lu 1713 fois, #0
Comment manipuler simplement le contenu d'un fichier WordML ?
  Manipulations autour du format WordML
par Fabien Reinle posté le 14/05/2008 à 23:55, lu 781 fois, #0
Polymorphisme et contrats de données WCF
  WCF aborde les types polymorphes du point de vue de la sérialisation. En effet, la connaissance du type réel potentiel est rendue nécessaire dès la description du contrat de données. Une fois n'est pas coutume, j'ai réalisé l'exemple en VB.NET.
par Frédéric Colin posté le 14/05/2008 à 08:40, lu 1881 fois, #2
A la découverte de BizTalk Server 2006 3/3
  Développer un assembleur pour BizTalk Server 2006 R2
par Kader Yildirim posté le 06/05/2008 à 13:20, lu 511 fois, #0

 Dernières Actualités      

Deep Earth – Une belle utilisation de Virtual Earth et de Silverlight Deep Zoom
  Ce projet très intéressant est disponible sur Codeplex et vous pouvez voir une démo sur la page suivante . Bien entendu comme touts les projets sur Codeplex vous avez accès aux sources....
Tags: Silverlight
Sortie de JetBrains ReSharper 4.0 en version finale, l’outil ultime pour Visual Studio
  Après plusieurs mois de Early Access Program (EAP) , JetBrains met enfin à disposition la version finale de son outil ReSharper 4.0 . Cette nouvelle version est disponible pour Visual Studio 2005 &...
Tags: Visual Studio 2008, Visual Studio 2005, Outils
BoutDuTunnel v1.4
  BoutDuTunnel est un petit logiciel de tunneling réseau écrit en C#. Il permet par exemple d’accéder aux services ftp/smtp/pop/telnet/nntp/… sur des réseaux qui n’autorisent...
BI Framework & sample sur CodePlex
  Après plusieurs demandes, je me suis décidé à déposer l'ensemble des sources et du BI Framework MS proposés dans mes articles sur codeplex : http://www.codeplex.com/BILAB Je le mettrais à jour au fil des...
Injection de code et API de profiling .NET
  Si vous êtes intéressés par la sécurité du Framework, par le reverse engineering et la manipulation/injection de code .NET et les packers, alors jetez un coup d’œil...
NDepend pour l'analyse statique de code .NET
  Pour ceux qui ne connaissent pas NDepend , il s’agit d’un outil d’analyse statique de code .NET qui permet de remonter des informations à toute une équipe de développement. NDepend aide à travailler sur...
Tags: Outils