Dot.Blog

Consulting DotNet C#, XAML, WinUI, WPF, MAUI, IA

Créer un arbre des répertoires avec XML (et l'interroger avec LINQ to XML)

Pour les besoins d'un prochain billet je voulais obtenir une structure arborescente de test sous la forme d'un fichier XML. Une telle chose peut se faire à la main mais cela est fastidieux et à force de copier/coller les données seront trop semblables et donc peu utilisables pour des tests et du debug, ce qui est dommage puisque tel était le but recherché... Pour générer des données aléatoires mais réalistes je possède déjà un outil fantastique (puisque c'est moi qui l'est fait :-) ), DataGen, un logiciel puissant mais qui n'est pas étudié pour générer des données dont la strucuture est récursive. Restait donc à concevoir un utilitaire pour satisfaire mon besoin de l'instant. Je n'aime pas développer "pour rien" donc il fallait que cet utilitaire en soit bien un. Tout disque dur de développeur contient une telle quantité de données que l'ensemble des répertoires forment une superbe structure arborescente ni trop petite ni trop gigantesque. Exactement ce qu'il faut pour des tests ! Ainsi, l'outil devra fabriquer un fichier XML à partir de n'importe quel niveau de répertoire, la structure étant hiérarchique. Je m'empresse donc de vous faire partager ce petit bout de code qui vous sera utile un jour ou l'autre je pense. using System; using System.IO; using System.Linq; using System.Xml.Linq;   namespace Enaxos.Tools.Xml { /// <summary> /// This class can generate an XML file from a folder hierarchy. /// </summary> public static class DirToXml { /// <summary> /// Builds the tree document. /// </summary> /// <param name="dirname">The name of the starting folder.</param> /// <returns>a LINQ to XML <c>XDocument</c></returns> public static XDocument BuildTreeDocument(string dirname) { return new XDocument(new XDeclaration("1.0", "utf-8", "yes"), new XComment("Structure au "+DateTime.Now),new XElement("directories", BuildTree(dirname))); }   /// <summary> /// Builds the tree (recursive method). /// </summary> /// <param name="dirName">Name of the starting folder.</param> /// <returns>a LINQ to XML <c>XElement</c> being the root (or 1st item) of the tree.</returns> public static XElement BuildTree(string dirName) { var di = new DirectoryInfo(dirName); var files = di.GetFiles(); var dirsize = 0l; foreach (var file in files) { dirsize += file.Length; } var subdirs = di.GetDirectories(); // each item is a "directory" having 5 attributes // name is the name of the folder // fullpath is the full path including the name of the folder // size is the size of all files in the folder (in bytes) // files is the number of files in the folder // subdirs is the count of possible sub directories in the folder var elem = new XElement("directory", new XAttribute("name", di.Name), new XAttribute("fullpath",dirName), new XAttribute("size", dirsize), new XAttribute("files",files.Count()), new XAttribute("subdirs",subdirs.Count())); foreach (var dinf in subdirs) { var elemDir = BuildTree(dirName + "\\" + dinf.Name); elem.Add(elemDir); }   return elem; } } } Ce code peut être utilisé sans passer par un fichier disque puisqu'on récupère le document en mémoire. L'exemple ci-dessous montre comment appeler le code et comment interroger la structure via Linq To Xml pour connaître les répertoires vides (n'ayant ni fichier ni sous répertoire) : 1: var d = DirToXml.BuildTreeDocument(@"D:\WpfToolkit"); 2: d.Save(@"d:\test.xml"); 3: Console.WriteLine(d.ToString()); 4: Console.WriteLine(new string('-',60)); 5:   6: var q = from e in d.Descendants("directory") 7: where (int) e.Attribute("files") == 0 8: && (int)e.Attribute("subdirs") == 0 9: orderby (string) e.Attribute("fullpath") 10: select e.Attribute("fullpath"); 11:   12: Console.WriteLine("Répertoires vides"); 13: foreach (var element in q) 14: { Console.WriteLine(element); } 15: Console.WriteLine(string.Format("{0} répertoires vides",q.Count()));   A bientôt pour un prochain billet, so.. Stay Tuned !

Class Helper, LINQ et fichiers CSV

C# 3.0 a apporté des nouveautés syntaxiques qui ne se bornent pas à être seulement des petits suppléments qu'on peut ignorer, au contraire il s'agit de modifications de premier plan qui impactent en profondeur la façon d'écrire du code si on sait tirer partie de ces nouveautés. J'en ai souvent parlé, au travers de billets de ce blog ou d'articles comme celui décrivant justement les nouveautés syntaxiques de C#3.0. Je ne vais donc pas faire de redites, je suppose que vous connaissez maintenant tous ces ajouts au langage. Ce que je veux vous montrer c'est qu'en utilisant correctement C# 3.0 on peut écrire un code incroyable concis et clair pour résoudre les problèmes qui se posent au quotidien au développeur. Le problème à résoudre Vous recevez un fichier CSV, disons le résultat d'une exportation depuis un soft XY des ventes à l'export de votre société. Vous devez rapidement ajouter la possibilité d'importer ces données dans l'une de vos applications mais après les avoir interprétées, triées, voire filtrées. La méthode la plus simple Rappel : un fichier CSV est formé de lignes comportant plusieurs champs séparés par des virgules. Les champs peuvent être délimités par des double quotes. A partir de cette description voyons comment avec une requête LINQ nous pouvons lire les données, les filtrer, les trier et les mettre en forme. Le but ici sera de générer en sortie une chaîne par enregistrement, chaîne contenant des champs "paddés" par des espaces pour créer un colonnage fixe. On tiendra compte des lignes débutant par le symbole dièse qui sont considérées comme des commentaires. 1: string[] lines = File.ReadAllLines("Export Sales Info-demo.csv"); 2: var t1 = lines 3: .Where(l => !l.StartsWith("#")) 4: .Select(l => l.Split(',')) 5: .OrderBy(items=>items[0]) 6: .Select(items => String.Format("{0}{1}{2}", 7: items[1].PadRight(15), 8: items[2].PadRight(30), 9: items[3].PadRight(10))); 10: var t2 = t1 11: .Select(l => l.ToUpper()); 12: foreach (var t in t2.Take(5)) 13: Console.WriteLine(t); La sortie (avec le fichier exemple fourni) sera : SAN JOSE       CITY CENTER LODGE             CA SAN FRANCISCO  KWIK-E-MART                   CA SEATTLE        LITTLE CORNER SWEETS          WA SEATTLE        LITTLE CORNER SWEETS          WA IRVINE         PENNY TREE FOODS CORPORATION  CA  Cette méthode, très simple, ne réclame rien d'autre que le code ci-dessus. La solution est applicable à tout moment et s'adapte facilement à toute sorte de fichiers sources. Elle possède malgré tout quelques faiblesses. Par exemple les champs contenant des doubles quotes ou les champs mal formés risquent de faire échouer la séquence. Dans certains cas cela sera inacceptable, dans d'autres on pourra parfaitement protéger la séquence dans un bloc try/catch et rejeter les fichiers mal formés. Une fois encore il ne s'agit pas ici de montrer un code parfaitement adapté à un problème précis, mais bien de montrer l'esprit, la façon d'utiliser C# 3.0 pour résoudre des problèmes courants.  Expliquons un peu ce code : la ligne 1 charge la totalité du fichier CSV dans un tableau de strings. La méthode peut sembler "brutale" mais en réalité elle est souvent très acceptable car de tels fichiers dépassent rarement les quelques dizaines ou centaines de Ko et la RAM de nos machines modernes n'impose en rien une lecture bufferisée, tout peut tenir d'un bloc en mémoire sans le moindre souci. Cela nous arrange ici il faut l'avouer mais l'utilisation d'un flux bufferisé resterait parfaitement possible. Nous disposons maintenant d'un tableau de chaînes, chacune étant formatée en CSV. La première requête LINQ (variable "t1" en ligne 2) fait l'essentiel du travail : gestion des commentaires (ligne 3) décomposition des champs (ligne 4) tri sur l'un des champs (ligne 5) génération d'une sortie formatée (lignes 6 à 9) Ce qui est merveilleux ici c'est que nous avons en quelques lignes un concentré de ce qu'est une application informatique : acquisition de données, traitement de ces données, production de sorties exploitables par un être humain ou une autre application. En si peu de lignes nous avons réalisé ce qui aurait nécessité une application entière avec les langages de la génération précédente comme C++ ou Delphi. C'est bien ce bond en avant que représente C# 3.0 qui est ici le vrai sujet du billet. Une méthode plus complète La séquence que nous avons vu plus haut répond au problème posé. Elle possède quelques lacunes. Celles liées à sa trop grande simplicité (certains cas du parsing CSV ne sont pas correctement pris en compte) et celles liées à sa forme : c'est un bout de code qu'il faudra copier/coller pour le réutiliser et qui viendra "polluer" nos requêtes LINQ les rendant plus difficiles à lire. Si ces lacunes sont acceptables dans certains cas (règlement ponctuel d'un problème ponctuel) dans d'autres cas on préfèrera une approche plus facilement réutilisable. Notamment si nous sommes amenés à traiter plus ou moins souvent des fichiers CSV nous avons intérêt à encapsuler le plus possible le parsing et à le rendre plus facilement reexploitable. L'une des voies serait de créer une classe totalement dédiée à cette tâche. C'est une solution envisageable mais elle est assez lourde et amène son lots de difficultés. Nous allons choisir ici une autre approche, celle de la création d'un class helper. C'est à dire que nous souhaitons non pas créer une classe qui traite un fichier CSV comme un tout, mais nous voulons pouvoir parser n'importe quelle chaîne de caractères formatée en CSV. L'approche est très différente. Dans le premier cas il nous faudra complexifier de plus en plus notre classe, voire créer une hiérarchie de classes pour traiter les fichiers CSV mais aussi les flux mémoire CSV, et puis encore les services Web retournant du CSV, etc, etc. Dans le second cas nous allons plutôt ajouter la capacité à la classe string de parser une chaîne donnée. La source de la chaîne ne nous importe pas. Il pourra s'agir d'un élément d'un tableau de chaîne comme dans le premier exemple autant que d'une chaîne lue depuis un flux mémoire, une section data d'un fichier XML, etc. D'un certain sens nous acceptons d'être moins "complet" que l'option "classe dédiée CSV", mais nous gagnons en agilité et en "réutilisabilité" en faisant abstraction de la provenance de la chaîne à parser. Bien entendu nous pouvons nous offrir le luxe d'une telle approche car nous savons que nous pouvons nous reposer sur C# 3.0, ses class helpers et LINQ... Le projet qui accompagne ce billet contient tout le code nécessaire et même un fichier CSV d'exemple, nous n'entrerons pas dans les détails de l'implémentation du class helper lui-même qui étend la classe string, parser du CSV n'est qu'un prétexte sans plus d'intérêt dans ce billet. Le code utilisé pour l'exemple provient d'ailleurs d'un billet de  Eric White dont vous pouvez visiter le blog si vous lisez l'anglais. la façon d'écrire un class helper est décrite dans mon article sur C# 3.0, regardons juste sa déclaration : public static string[] CsvSplit(this String source) Cette méthode est déclarée au sein d'une classe statique de type "boîte à outils" pouvant centraliser d'autres méthodes utilitaires. La déclaration nous montre que le class helper s'applique à la classe String uniquement (this String source) et qu'elle retourne un tableau de chaîne (string[]). Une fois le class helper défini (code complet dans le projet accompagnant l'article) il nous est possible d'écrire des requêtes LINQ du même type que celle du premier exemple. Mais cette fois-ci le parsing CSV est réalisé par le class helper ce qui permet à la fois de le rendre plus sophistiqué et surtout de ne plus avoir à le copier/coller dans les requêtes LINQ... Voici un exemple d'utilisation du class helper sur le même fichier CSV. Ici nous parsons la source, nous la trions, nous éliminons les lignes de commentaire mais aussi nous créons en réponse une classe anonyme contenant le nom du contact, sa ville et le nom de sa société. Il est dès lors possible de traiter la liste d'objets résultat comme n'importe quelle liste : affichage, traitement, génération d'état, etc. 1: var data = File.ReadAllLines("Export Sales Info-demo.csv").Where(s=>!s.StartsWith("#")) 2: .Select(l => 3: { 4: var split = l.CsvSplit(); 5: return new 6: { 7: Contact = split[0], 8: City = split[1], 9: Company = split[2] 10: }; 11: } ).OrderBy(x=>x.Contact); 12:   13: foreach (var d in data.Take(5)) 14: Console.WriteLine(d); Ce qui, avec le même fichier source, affichera à la console : { Contact = Allen James, City = San Jose, Company = City Center Lodge } { Contact = Bart H. Perryman, City = San Francisco, Company = Kwik-e-mart } { Contact = Beth Munin, City = Seattle, Company = Little Corner Sweets } { Contact = Beth Munin, City = Seattle, Company = Little Corner Sweets } { Contact = Bruce Calaway, City = Irvine, Company = Penny Tree Foods Corporation }  Conclusion La bonne utilisation de C# 3.0 permet de réduire significativement la taille du code donc de réduire dans les mêmes proportions les bugs et d'augmenter la productivité. Apprendre à utiliser cette nouvelle approche c'est gagner sur tous les plans...   Le projet exemple : LinqToCsv.zip (9,46 kb)

Un éclairage sur les techniques d'accès aux données sous .NET

Depuis la sortie de .NET Microsoft n'arrête plus sa course folle ! La plupart des technologies publiées faisaient partie d'un plan presque totalement connu à l'avance, comme WPF, WCF etc. Il a fallu du temps pour qu'émerge ses "modules" de .NET car même le plus puissant des éditeurs de logiciels du monde ne peut pas releaser la totalité d'une montagne comme .NET en un seul morceau. S'il était clair que WPF serait la nouvelle gestion des interfaces utilisateurs et que Windows Forms n'était qu'un "os à ronger" en attendant, le foisonnement des technologies tournant autour des données n'était pas forcément visible ni prévisible il y a quelques années. Et même aujourd'hui savoir ce qui existe et comment s'en servir avec l'ensemble des autres technologies n'est pas forcément une mince affaire ! Un petit dessin valant tous les discours, voici un diagramme qui tourne sur les blogs américains de Microsoft et que j'ai en partie traduit pour vous, en espérant que cela vous aidera à y voir plus clair ! Quelques précisions pour certains acronymes ou noms de technologies : EDM : Entity Data Model - Modèles de données de l'Entity Framework (EF) RDBMS : SGBD, une base de données ASP.NET Dynamic Data : nouveau système de .NET 3.5 SP1 simplifiant et améliorant la prise en charge des données dynamiques dans ASP.NET ASP.NET MVC : Nouvelle couche permettant d'appliquer le paradigme Modèle-Vue-Controleur aux développement ASP.NET ADO.NET Data Services : couche de communication transformant un modèle EDM en un service Web [Faite un clic-droit sur l'image et copiez-la pour l'afficher en 100% dans Word ou autre ]

LINQ to {tapez ce que vous voulez ici}. Des providers à foison !

Vous connaissez LINQ to Object, LINQ to Dataset, LINQ to XML, etc... Microsoft livre déjà une large palette de fournisseurs de données (providers) pour LINQ. Dans sa grande sagesse Microsoft a aussi publié les spécifications (et des exemples) permettant à tous de développer des fournisseurs de données pouvant fonctionner avec LINQ. Cette ouverture extraordinaire permet d'interroger avec LINQ des données de toute sorte sans avoir besoin de les transformer au préalable. Mais savez-vous qu'à ce jour il existe déjà plus d'une trentaine de providers LINQ ? Et que forcément certains vous rendrons des services inestimables, à condition d'en connaître l'existence... Vous pouvez même écrire vos propres providers LINQ ! Je vous conseille d'ailleurs cette page du Wayward blog qui propose une série de billets expliquant comment créer un tel provider. Un exemple parmi tant d'autres pour illustrer le propos : LINQ To Amazon. Il permet tout simplement d'interroger directement Amazon pour sélectionner des livres selon autant de critères que nécessaire, et sans autre programmation qu'une requête LINQ. Fantastique non ? Un petit exemple pour concrétiser la chose : var query =   from book in new Amazon.BookSearch()   where     book.Title.Contains("ajax") &&     (book.Publisher == "Manning") &&     (book.Price <= 25) &&     (book.Condition == BookCondition.New)   select book;   Cool non ? LINQ to Amazon est présenté dans le livre LINQ in Action (que je vous conseille au passage) et vous pouvez accéder au blog de Fabrice qui le présente. Mais cela n'est qu'un exemple, et il existe bien d'autres fournisseurs de données LINQ, en voici une liste (avec liens - shift clic pour les ouvrir dans une nouvelle fenêtre) : LINQ to Amazon LINQ to Active Directory LINQ to Bindable Sources (SyncLINQ) LINQ to C# project LINQ to Continuous Data (CLinq) LINQ to CRM LINQ To Geo - Language Integrated Query for Geospatial Data LINQ to Excel LINQ to Expressions (MetaLinq) LINQ Extender (Toolkit for building LINQ Providers) LINQ to Flickr LINQ to Google LINQ to Indexes (LINQ and i40) LINQ to IQueryable (Matt Warren on Providers) LINQ to JSON LINQ to LDAP LINQ to NHibernate LINQ to JavaScript LINQ to LLBLGen Pro LINQ to Lucene LINQ to Metaweb(freebase) LINQ to MySQL, Oracle and PostgreSql (DbLinq) LINQ to NCover LINQ to Opf3 LINQ to Parallel (PLINQ) LINQ to RDF Files LINQ to Sharepoint LINQ to SimpleDB LINQ to Streams LINQ to WebQueries LINQ to WMI http://tomasp.net/blog/linq-expand.aspx http://tomasp.net/blog/linq-expand-update.aspx Linq To WIQL LINQ to XtraGrid Charlie Calvert, que les delphistes connaissent bien, et qui comme bon nombre des meilleurs a depuis longtemps choisi la voie .NET avec Microsoft, met à jour régulièrement cette liste qui est issue de son blog. Si vous lisez l'anglais, bookmarquez la page Links to LINQ de son blog qui, en plus de la liste des providers, fournit de nombreux liens en rapport avec cette technologie. Conclusion LINQ est une technologie fantastique dont je ne cesse de dire du bien (et d'utiliser avec bonheur dans la totalité des applications que j'écris), je ne peux imaginer utiliser demain ou dans 10 ans un langage qui n'implémentera pas une feature de même type. LINQ to "n'importe quoi" est la preuve que cette technologie est en plus ouverte et que seule notre imagination est la limite. Borland utilisait le slogan "the sky is the limit" durant ses grandes années de gloire. Microsoft ne le dit pas, mais la firme de Redmond rend ce rêve possible aujourd'hui...  

Un outil gratuit pour tester les requêtes LINQ

Qu'il s'agisse de LINQ to Object ou LINQ to SQL il serait bien agréable de pouvoir disposer d'une sorte de bloc-note pour tester ou mettre au point des requêtes, voire pour s'entraîner tout simplement. Pouvoir gérer les connexions aux bases de données, éventuellement envoyer et tester du SQL "de base" sur celles-ci serait la cerise sur la gâteau. Et bien cet outil existe, et en plus il est gratuit !   LINQPad, puisque c'est de lui qu'il s'agit, n'est pas vraiment une nouveauté absolue mais il y a fort à parier que nombre d'entre vous n'en ont jamais entendu parlé. Cet outil a été développé conjointement à l'excellent ouvrage "C# 3.0 in a nutshell" de Joseph Albahari, un australien, qui par la diffusion gratuite de LINQPad tente une promo plutôt positive de son livre et de ses services de consulting (si on habite au pays des kangourous, of course). J'aime personnellement cette idée de "pub positive" qui consiste à offrir de son temps et de son travail pour faire sa pub plutôt que de gaver les gens de bandeaux agaçants. C'est de la pub dans un esprit totalement anti-google (de la pub pour de la pub, partout, sans rien apporter à ceux qui la subissent), et il faut encourager ceux qui adoptent cette façon respectueuse de faire leur promotion. Achetez le livre de Joseph ! Fin de la paranthèse :-) LINQPad est ainsi un super utilitaire. Il interprète du code C# ou VB et les requêtes LINQ to Object ou LINQ to SQL. Il autorise la connexion à des bases de données, offre un éditeur purement SQL en supplément, bien pratique, et il permet même d'ajouter ses propres DLL et ses propres espaces de noms pour qu'ils soient reconnus dans le code C# qu'on tape ! On peut dès lors utiliser LINQPad pour requêter des objets métiers complexes, un framework maison, un modèle LINQ to SQL, etc.. Il suffit d'ajouter les références. Pour terminer, LINQPad est fourni avec de nombreux exemples qui sont autant de moyens de tester ses connaissances et d'apprendre les subtilités de LINQ. Utilisé conjointement à ma version des "101 exemples LINQ" de Microsoft, LINQPad devient un fantastique outil d'auto-formation. Pour télécharger le produit et avoir accès aussi aux forums, aux vidéos de démo, et plein d'autres choses, aller sur la page de LINQPad ! Bon Dev... Et Stay Tuned !