blog.pagesd.info

Aller au contenu | Aller au menu | Aller à la recherche

mardi 23 février 2010

Convertir SQL en LINQ : FROM et SELECT

Ceci est une traduction assez libre de la série de billets rédigés par Bill Horst pour apprendre comment passer du langage SQL au langage LINQ et plus spécialement en VB LINQ. Pour ma part, j’ai adapté les explications et les exemples de codes pour cibler la syntaxe C# du langage LINQ.

Après un premier billet pour comparer SQL et LINQ, ce deuxième billet de la série va présenter comment gérer des clauses plus spécifiques du SQL, en commençant par deux des clauses les plus importantes : FROM et SELECT.

La clause FROM

Une commande SQL SELECT débute toujours par une clause SELECT suivie d’une clause FROM. Dans le cas de LINQ, une expression commence toujours par une clause « from » (ou « aggregate » comme nous le verrons par la suite). La clause SQL FROM de base indique la table sur laquelle va porter la requête, et de façon similaire, la clause LINQ from indique l’objet sur lequel va porter la requête (ClientListe dans notre exemple).

Cet objet peut représenter des données en mémoire, des données d’une table SQL ou des données XML. Dans les exemples à venir, j’utilise des données « en mémoire » pour que le code soit suffisamment simple. En plus de cet objet sur lequel porte la requête, la clause LINQ from contient toujours un identifiant pour la « ligne » courante (Contact dans notre exemple) qui sert d’alias pour représenter cette ligne.

Avec SQL, il suffit de faire « SELECT * » pour sélectionner toutes les colonnes de la table. En ce qui concerne LINQ, il suffit d’utiliser la clause « select alias » pour renvoyer tous les membres de l’objet requêté :

SQL :

SELECT *
FROM   ClientTable

LINQ :

from Contact in ClientListe
select Contact

Utiliser un alias dans le FROM

En SQL, il est possible de définir un alias sur une table au niveau de la partie FROM de la requête. Cela permet ensuite de faire référence aux colonnes de la table source par l’intermédiaire de cet alias. Comme nous l’avons vu dans le paragraphe précédent, l’identifiant qui est défini au niveau de la clause from en LINQ sert lui aussi essentiellement à cela :

SQL :

SELECT Contact.ClientID, Contact.Telephone
FROM   ClientTable Contact

LINQ :

from Contact in ClientListe
select new { Contact.ClientID, Contact.Telephone }

La clause SELECT

En SQL, les commandes SELECT contiennent une liste des valeurs à sélectionner parmi les informations disponibles (Nom, Telephone, Ville…). De la même façon, le langage LINQ vous permet lui aussi de sélectionner certains des membres pour renvoyer un objet anonyme constitué à partir de cette sélection.

Il n’est absolument pas nécessaire que les membres que vous définissez correspondent à l’objet que vous avez spécifié au niveau de la clause from. Il peut en effet s’agir de n’importe quelle expression C# valide (comme par exemple « 3 + 4 »). S’il n’est pas possible de déduire le nom d’un membre que vous avez sélectionné, vous devrez utiliser un alias (voir le paragraphe suivant).

SQL :

SELECT Nom, ClientID
FROM   ClientTable Contact

LINQ :

from Contact in ClientListe
select new { Contact.Nom, Contact.ClientID }

Utiliser un alias dans le SELECT

Toujours en SQL, il est possible de définir des alias pour les différents éléments que vous sélectionnez au niveau de la clause SELECT, ce qui permet ensuite d’utiliser ces alias pour faire référence à ces éléments. De la même façon, LINQ vous permet de donner un nom aux éléments que vous sélectionnez, ce qui vous permettra par la suite d’utiliser ce nom faire référence à ces éléments lorsque vous exploiterez le résultat de la requête LINQ.

SQL :

SELECT Nom NomContact, ClientID ContactID
FROM   ClientTable Contact

LINQ :

from Contact in ClientListe
select new { NomContact = Contact.Nom, ContactID = Contact.ClientID }

Dans le prochain billet, je présenterai les équivalents des clauses DISTINCT, WHERE et ORDER BY pour le langage LINQ.

lundi 22 février 2010

Convertir SQL en LINQ : Introduction

Ceci est une traduction assez libre de la série de billets rédigés par Bill Horst pour apprendre comment passer du langage SQL au langage LINQ et plus spécialement en VB LINQ. Pour ma part, j’ai adapté les explications et les exemples de codes pour cibler la syntaxe C# du langage LINQ.

Comme vous le savez sans doute déjà, les commandes LINQ permettent d’écrire des requêtes en C# en utilisant une syntaxe inspirée de SQL. Cependant, la syntaxe de LINQ ne correspond pas exactement à la syntaxe du SQL. Par conséquent, si vous avez déjà travaillé en SQL ou que vous êtes habitués à écrire des requêtes SQL, vous allez sans doute chercher à transformer vos requêtes SQL en requêtes LINQ.

Ce billet va être le premier d’une série consacrée à la façon de traduire du SQL en LINQ. Dans cette introduction, je souhaite présenter ce qui différencie LINQ de SQL puis j’aborderai les particularités de chaque syntaxe dans la suite de la série.

Pré-requis

Les exemples de code SQL que je vais utiliser seront basés sur une table ClientTable et une table CommandeTable. Pour les exemples C#, je m’appuierai sur des objets ClientListe et les objets CommandeListe, tous deux de type IEnumerable. Et j’utiliserai deux classes, Client et Commande qui sont définies de la façon suivante :

class Client
{
    public int ClientID;
    public string Nom;
    public string Telephone;
    public string Adresse;
    public string Ville;
    public string CodePostal;
    public string Region;
}

class Commande
{
    public int CommandeID;
    public int ClientID;
    public Single Cout;
    public string Telephone;
    public DateTime DateCommande;
    public string Livraison;
    public string NomArticle;
}

Syntaxe de base

LINQ gère l’équivalent des commandes SQL SELECT, mais pas les autres types de commandes SQL telles que CREATE, INSERT, UPDATE ou DELETE. On peut considérer que la syntaxe de base d’une requête SQL SELECT est constituée d’une série de « clauses » débutant par une clause SELECT :

sqlSelectClause [ sqlClause1 [ sqlClause2 [ ... ] ] ]

Il peut exister un certain nombre de différences en fonction des nombreuses versions de SQL, mais on a toujours plus ou moins la même syntaxe générale :

SELECT Nom NomContact, ClientID ContactID
FROM   ClientTable
ORDER BY Nom

La syntaxe de base pour une expression LINQ consiste également en une série de « clauses » qui débute par la clause « from » (ou éventuellement une clause « aggregate » comme nous le verrons plus tard) :

linqFromClause [ linqClause1 [ linqClause2 [ ... ] ] ]

Ce qui donne par exemple :

from Contact in ClientListe
orderby Contact.Nom
select new { NomContact = Contact.Nom, ContactID = Contact.ClientID }

J’ai employé le terme « expression LINQ » ci-dessus parce qu’à proprement parler les requêtes LINQ ne sont pas des commandes complètes. Alors qu’une requête SQL se suffit à elle-même, si on se place d’un point de vue syntaxique, une requête LINQ n’est que l’équivalent d’une expression telle que « 3 * 4 ». Cela ne constitue pas une commande complète et il faut donc « faire » quelque chose avec. Une requête LINQ peut apparaitre dans du code C# tel que celui-ci :

var ContactsTries = from Contact in ClientListe
                    orderby Contact.Nom
                    select new { NomContact = Contact.Nom, ContactID = Contact.ClientID };

Chaque clause d’une requête LINQ porte sur un objet de type IEnumerable<T> et renvoie un nouvel objet de type IEnumerable<T> (sans que le type T retourné soit nécessairement du même type que le T initial). En général, les clauses de la requête sont analogues aux clauses SQL (SELECT ou ORDER BY par exemple) et vous pouvez habituellement traduire votre requête SQL en LINQ en faisant du « clause à clause ». Dans les exemples ci-dessus, même si les clauses apparaissent dans un ordre légèrement différent, vous pouvez malgré tout voir qu’elles sont assez semblables.

Conclusion

Après cette introduction assez généraliste, j’aborderai des aspects plus spécifiques au cours des billets suivants. Mon objectif tout au long de ces petits tutoriels consacrés à la traduction du SQL en LINQ est de présenter les points suivants :

  • FROM et SELECT
  • DISTINCT, WHERE et ORDER BY
  • Les fonctions (scalaires et agrégations)
  • GROUP BY et HAVING
  • Les jointures
  • UNION, TOP et sous-requêtes

mercredi 14 octobre 2009

Les types implicites en c#

Le compilateur C# 3.0 permet de déclarer les variables de façon implicite grâce au nouveau mot clé "var". Au lieu de nous forcer à taper le code suivant :

MonTypeSpecial toto = new MonTypeSpecial();

Il est possible d'économiser quelques touches en tapant seulement :

var toto = new MonTypeSpecial();

Dans les deux cas, le résultat est strictement identique et la variable toto est bien toujours une variable de type MonTypeSpecial, qu'elle soit déclarée explicitement ou implicitement. Dans la pratique, le code compilé est également rigoureusement le même.

Lors de la compilation, le compilateur comprend que new MonTypeSpecial() est une expression pour initialiser une variable de type "MonTypeSpecial". Et comme le type de la variable à laquelle on affecte cette expression n'est pas défini, il lui attribue implicitement le même type.

Par conséquent, ce n'est en aucune manière une incursion du C# dans le monde des langages dynamiques ou faiblement typés et le mot clé "var" n'a rien à voir avec la déclaration de variables de type Variant qui existait en Visual Basic.

La mot clé "var" peut être utilisé dans tous les cas où la variable déclarée est initialisée, quelque soit le type de l'expression qui sert à l'initialisation.

var i = 0;                           // i est un int
var j = i;                           // j est un int
var k = i + 10;                      // k est un int
var d = 12.34;                       // d est un double
var q = d / 2;                       // q est un double
var a = k / 3;                       // a est un int (int /int => int)
var f = (float)i;                    // f est un float
var t = "toto";                      // t est un string
var l = t.Length();                  // l est un int
var x = new DateTime(1980, 1, 1);    // x est une DateTime

Par contre, le code suivant ne compilera même pas :

// Impossible d'assigner <null> à une variable locale implicitement typée
var n = null;
// Les variables locales implicitement typées doivent être initialisées
var i;
i = 0;

Par ailleurs, on ne peut employer "var" qu’avec des variables locales et en aucun cas :

  • pour des variables publiques
  • en tant que paramètre de fonction
  • comme valeur de retour d'une méthode
  • comme type d'une propriété

=> Le mot clé contextuel 'var' ne peut apparaître que dans une déclaration de variable locale

Sinon, il (me) semble préférable de réserver l'utilisation des déclarations implicites au cas où l'expression d'initialisation indique en toute lettre le type de l'expression et de les éviter quand cela demande de démarrer le cerveau :

// Ok
var toto = new MonTypeSpecial();
var x = new DateTime(1980, 1, 1);
// Bof
var i = 0;
var j = i;
var t = "toto";
var l = t.Length();

jeudi 23 juillet 2009

Les types nullables en c#

Avec le compilateur C# 1.1, une variable de type valeur (comme un bool, un int, un double... mais aussi une DateTime) ne pouvait pas prendre la valeur null.

  • Quand on déclare une variable de type valeur, il faut obligatoirement lui affecter une valeur avant de l'utiliser, sans quoi on obtient une erreur de compilation : Utilisation d'une variable non assignée.
int compteur;
if (compteur == 0) {
        Console.WriteLine("zéro");
}
  • Il est impossible de lui attribuer la valeur null, car dans ce cas on obtient l'erreur Impossible de convertir une valeur Null en 'int', car il s'agit d'un type valeur.
int compteur = null;
if (compteur == 0) {
        Console.WriteLine("zéro");
}
  • Et bien sûr, on ne peut pas la comparer à la valeur null : L'opérateur '==' ne peut pas être appliqué aux opérandes de type 'int' et '<null>'
int compteur = 0;
if (compteur == null) {
        Console.WriteLine("null");
}

Avec le compilateur C# 2.0, il est désormais possible de faire en sorte que des variables de type valeur acceptent aussi une valeur nulle en plus des valeurs qu'elles peuvent prendre habituellement. Par exemple, une variable nullable de booléen pourra stocker true, false et null. Outre le fait que c'est une aubaine pour les indécis, c'est aussi censé être très pratique pour stocker les valeurs indéfinies en provenance d'une base de données.

Pour qu'un type de valeur devienne nullable, il faut suffixer le type valeur habituel avec un "?" :

int? compteur = null;
bool? ca_marche = null;
DateTime? pour_quand = null;

En fait, les types nullables sont des instances du struct System.Nullable. Par conséquent, les trois déclarations précédentes sont rigoureusement équivalentes aux trois lignes ci-dessous :

Nullable<int> compteur = null;
Nullable<bool> ca_marche = null;
Nullable<DateTime> pour_quand = null;

Les variables nullables ont une propriété HasValue qui renvoie true lorsque la variable contient une valeur et false quand la variable est nulle.

bool? ca_marche = null;
...
if (ca_marche.HasValue == false) {
        Console.Write("Pas de réponse");
} else if (ca_marche == true) {
        Console.Write("Oui");
} else {
        Console.Write("Non");
}

Elles ont aussi une méthode GetValueOrDefault() qui renvoie :

  • la valeur de la variable quand celle-ci n'est pas nulle (soit GetValueOrDefault())
  • la valeur par défaut pour le type de valeur correspondant lorsque la variable est nulle (soit GetValueOrDefault())

C'est ce qui permet par exemple d'affecter une variable de type nullable à une variable de type valeur correspondant :

int? toto = null;

int tutu;
tutu = toto.GetValueOrDefault();
Console.Write(tutu); // affiche 0

toto = 12;

int titi;
titi = toto.GetValueOrDefault();
Console.Write(titi); // affiche 12

On peut aussi utiliser le nouvel opérateur ?? pour définir la valeur par défaut qui sera utilisée dans le cas où on assignerait une variable de type nullable ayant la valeur nulle à une variable de type valeur non nullable :

int? toto = null;

int tutu;
tutu = toto ?? 0;
Console.Write(tutu); // affiche 0
int titi = toto ?? 12;
Console.Write(titi); // affiche 12

Par contre, il y aura une erreur de compilation si on essaie d'affecter directement une variable de type nullable à une variable de type valeur :

int? toto = null;
int tutu = toto; // Erreur de compilation: Cannot implicitly convert type 'int?' to 'int.
int? titi = 12;
tutu = titi; // Erreur de compilation: Cannot implicitly convert type 'int?' to 'int.

A la rigueur, il est possible de caster la variable de type nullable à une variable de type valeur. Cela ne provoquera pas d'erreur de compilation mais attendra l'exécution pour planter :

int? toto = null;
int tutu = (int)toto; // Compile mais provoque une exception de type InvalidCastException à l'exécution

Documentations sur les types nullables :

jeudi 25 juin 2009

Les propriétés automatiques du c#

Avec le compilateur de C# 3.0, il est possible d'avoir des propriétés automatiques. En fait, c'est tout simplement une nouvelle façon de déclarer les propriétés qui permet d'avoir un code source beaucoup plus concis.

Jusqu'à maintenant, quand j'ai besoin de gérer des propriétés dans une classe en C# 1.1, j'écris plus ou moins le code suivant :

private int _Id;
private string _Nom;
private string _Prenom;

public property int Id {
        get { return _Id; }
        set { _Id =  value; }
}

public property string Nom {
        get { return _Nom; }
        set { _Nom = value; }
}

public property string Prenom {
        get { return _Prenom; }
        set { _Prenom = value; }
}

Avec Visual Studio 2008 et C# 3.0, tout ça c'est du passé et grâce aux propriétés automatiques je pourrai à l'avenir ramener ça à 3 lignes de codes :

public property int Id { get; set; }
public property string Nom { get; set; }
public property string Prenom { get; set; }

Dans ce cas, c'est le compilateur qui va faire le boulot à ma place (y'a rien à faire !) et générer peu ou prou tout le code que je tapais auparavant moi-même.

Par conséquent, si on regarde ça du point de vue du code compilé, ce n'est pas du tout la même chose que de se contenter de déclarer de simples variables publiques :

public int Id;
public string Nom;
public string Prenom;

Ma conversion à l'utilisation de propriétés plutôt que des variables publiques remonte à tellement longtemps que j'ai un peu oublié pourquoi j'avais trouvé les propriétés plus mieux que les variables. Ce qui fait qu'aujourd'hui j'ai un peu de mal à voir ce que ces nouvelles propriétés automatiques m'apportent réellement de plus que de bêtes variables publiques... (et apparemment je ne suis pas le seul)

Ou alors, à la rigueur, ce qui est plus intéressant c'est qu'avec cette méthode il est possible définir des propriétés en lecture seule (voire en écriture seule ?), ce que je ne peux pas absolument pas faire avec des variables. Pour cela, il ne suffit pas d'omettre le "set;" (ça aurait été trop simple), mais il faut le rendre "private" afin qu'il ne soit pas visible et donc utilisable en dehors de la classe :

public property int Id { get; private set; }    // lecture seule
public property string Nom { get; set; }
public property string Prenom { get; set; }seule

Dans ce cas, je peux définir la propriété "Id" dans tout le code source à l'intérieur de ma classe, et par contre elle n'est pas modifiable par tout le code en dehors de cette classe.

Mise à jour après coup

Le fait d'avoir un "set" privé n'est pas limité aux propriétés automatiques. Cela existe depuis C# 2.0 pour toutes les propriétés. L'avantage c'est que ça permet d'avoir du code plus homogène dans la mesure où cela permet d'éviter que le code de la classe utilise dans certains cas les variables privés et dans d'autres cas les propriétés.

Avec C# 1.1, même si on se forçait à utiliser partout les propriétés à l'intérieur de la classe, il fallait passer par les variables privées dès qu'on voulait avoir avoir des propriétés en lecture seules (c'est à dire sans set). Avec un private set, on peut donc avoir une propriété en lecture seule et malgré tout l'utiliser en écriture à l'intérieur de la classe.

mardi 28 avril 2009

Pinguer une Url

Il est quelquefois très pratique de pouvoir exécuter un traitement sur un site web. Par exemple, dans PI il existe une page pour sauvegarder automatiquement la base de données. Le problème, c'est que sous Windows, il n'est pas possible de faire une tache planifiée qui appelle directement une page web.

J'avais écrit il y a quelques temps un petit utilitaire UrlPing pour faire une requête sur une page web en indiquant simplement l'url à atteindre. Tout marchait plutôt bien depuis plus d'un an jusqu'à ce qu'hier j'essaie de l'utiliser avec une url qui ne correspond pas à un "vrai" fichier .aspx mais à une url "rewritée". Et là, cela provoque une erreur du type "Tentatives de redirection automatique trop nombreuses".

J'en ai donc profité pour améliorer le code et le publier si jamais cela intéresse quelqu'un.

namespace Altrr.Tools.UrlPing {

  using System;
  using System.Net;

  class Start {

    [STAThread]
    static void Main(string[] args) {

      if (args.Length == 1) {
        string url = args[0];
        Console.Write(url + " : ");
        try {
          HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(url);
          request.CookieContainer = new CookieContainer();
          request.Method = "HEAD";
          HttpWebResponse response = (HttpWebResponse)request.GetResponse();
          Console.WriteLine("OK (" + Convert.ToInt32(response.StatusCode) + " - " + response.StatusDescription + ")");
          string urlr = response.ResponseUri.ToString();
          if (url != urlr) {
            Console.WriteLine("-------\n" + urlr);
          }
        } catch (Exception ex) {
          Console.WriteLine("KO\n-------\n" + ex.Message);
        }
      } else {
        Console.WriteLine("Syntax : UrlPing url");
      }

    }

  }
}

mardi 21 avril 2009

System.Net.Mail et smtp.gmail.com

Poursuivant mes essais de migration d'Altrr-Press vers ASP.NET 2.0, j'ai enfin étudié comment remplacer System.Web.Mail par System.Net.Mail. Jusqu'à présent, j'utilisais la méthode décrite dans mon plus célèbre billet System.Web.Mail and smtp.gmail.com.

Par rapport à cette méthode, les modifications à apporter n'ont finalement pas été si compliquées que ça :

public string sendMail (string from, string to, string cc, string bcc, string subject, string body) {

  // Mail initialization
  MailMessage mail = new MailMessage();
  mail.From = new MailAddress(from);
  mail.To.Add(to);
  if (cc != "") {
    mail.Cc.Add(new MailAddress(cc));
  }
  if (bcc != "") {
    mail.Bcc.Add(new MailAddress(bcc));
  }
  mail.Subject = subject;
  mail.IsBodyHtml = false;
  mail.BodyEncoding = System.Text.Encoding.UTF8;
  mail.Body = body;

  // Smtp configuration
  SmtpClient smtp = new SmtpClient();
  smtp.Host = "smtp.gmail.com";
  // - smtp.gmail.com use smtp authentication
  smtp.Credentials = new NetworkCredential("myemail@gmail.com", "mypassword");
  // - smtp.gmail.com with System.Net.Mail accepts port 25 or 587
  smtp.Port = 25;
  // - smtp.gmail.com use STARTTLS (some clients call this SSL)
  smtp.EnableSsl = true;

  // Mail sending
  try {
    smtp.Send(mail);
    return "";
  } catch (Exception ex) {
    return ex.Message;
  }
}

Une première petite difficulté est venu des propriétés From, To, Cc et Bcc de l'objet MailMessage qui ne sont plus de type chaine comme en .NET 1 mais du type MailAddress ou collection de MailAddress.

Mais le plus gros problème, c'est qu'avec System.Net.Mail, je n'ai jamais réussi à utiliser le port 465 alors qu'il fonctionne très bien avec System.Web.Mail (et même sur un site en .NET 2 qui utilise encore System.Web.Mail !!!). Et comme en local le port 587 n'est pas ouvert, j'ai un peu trainé pour faire mes tests puisque je devais à chaque fois faire une mise en production afin de vérifier si cela fonctionnait.

Mais au final, j'ai quand même découvert qu'avec System.Net.Mail :

  • cela fonctionne aussi bien avec le port 587 qu'avec le port 25 (!)
  • il n'est même pas nécessaire de définir la propriété smtp.Port pour que le mail parte (re !)

mardi 14 avril 2009

Zéro erreur avec StyleCop

Après une lutte presque acharnée, j'ai enfin réussi à faire passer haut la main tous les contrôles de StyleCop à mon projet BDHelper. Il s'agit du projet qui regroupe tout ce qui concerne l'accès aux bases de données dans Altrr-Press.

Un tiers de boulot

Pour en arriver là, j'ai dû commencer par faire beaucoup de modifications à la main et surtout ajouter des commentaires en pagaille. Personnellement, j'en met là où cela me parait nécessaire pour comprendre ce qui se passe, mais avec StyleCop il faut vraiment en mettre de partout !

Un autre truc à faire c'était de remplacer tous les "" par des string.Empty, en faisant bien attention de ne pas toucher aux "" dans les scripts javascript sinon c'est tout cassé.

Et pour finir, j'ai dû faire une croix sur les variables locales préfixées par un "_". Donc, plus de :

public string cnxString {
    get {
        return _cnxString;
    }
    set {
        _cnxString = value;
    }
}
private string _cnxString = "";

Mais à la place :

public string CnxString {
    get {
        return this.cnxString;
    }
    set {
        this.cnxString = value;
    }
}
private string cnxString = string.Empty;

Un tiers d'artistique

Une fois cette première étape réalisée, il ne me restait quasiment plus que des problèmes de présentation. Grosso modo, il fallait que je trouve un moyen pour re-formater mon code de ça :

public string LastSQL {
    get {
        return this.lastSQL;
    }
    set {
        this.lastSQL = value;
    }
}

En ça :

public string LastSQL
{
    get
    {
        return this.lastSQL;
    }

    set
    {
        this.lastSQL = value;
    }
}

C'est à dire :

  • placer les accolades ouvrantes sur une nouvelle ligne,
  • faire un saut de ligne après un accolade fermante,
  • et ce qui se voit un peu moins, remplacer les tabulations par des espaces.

Outre le fait que cette façon d'écrire le code est un peu contraire à mes convictions, ça risquait d'être assez coton de faire ça à la main.

Et c'est là que Artistic Style entre en jeu. C'est un petit utilitaire en open source capable de re-formater les fichiers sources C, C++, C# et Java.

Grace à lui, j'ai pu assez vite obtenir la présentation exigée par StyleCop, en définissant seulement le paramètre --style=ansi. Celui-ci permet d'appliquer un formatage du code selon le style pré-défini "ANSI", à savoir :

  • les accolades sont placées sur des lignes à part,
  • le code est indenté sur 4 espaces (remplacement des tabulations mais aussi correction des indentations incorrectes),
  • les éléments namespace, classe et switch ne sont pas indentés.

Ce qui permet d'obtenir ce style de code :

namespace foospace
{
int Foo()
{
    if (isBar)
    {
        bar();
        return 1;
    }
    else
        return 0;
}
}

Un tiers d'expression régulière

Après un passage éclair par Artistic Style, le nombre d'anomalies pour le projet BDHelper est tombé à seulement 30 avertissements. Déjà, rien que le fait d'avoir une simple ligne catch {} provoque 4 avertissements :

  • Layout rule SA1501 : A C# statement containing opening and closing curly brackets is written completely on a single line
  • Readability rule SA1107 : The C# code contains more than one statement on a single line
  • Spacing rule SA1012 : An opening curly bracket within a C# element is not spaced correctly
  • Spacing rule SA1013 : A closing curly bracket within a C# element is not spaced correctly

Pour franchir cet ultime pallier, j'ai tout simplement utilisé Visual Studio et la possibilité qu'il offre de faire des remplacements à base d'expressions régulières.

Pour traiter les 3 cas où la ligne "catch {}" apparait, j'ai effectué le remplacement suivant :

  • Rechercher : ^{ *}catch \{\}
  • Remplacer par : \1catch\n\1{\n\1}

Après ce premier remplacement, il ne me reste plus que 18 avertissements, correspondants aux 4 règles suivantes :

  • Layout rule SA1505 : An opening curly bracket within a C# element, statement, or expression is followed by a blank line
  • Layout rule SA1506 : An element documentation header above a C# element is followed by a blank line
  • Layout rule SA1508 : A closing curly bracket within a C# element, statement, or expression is preceded by a blank line
  • Layout rule SA1512 : A single-line comment within C# code is followed by a blank line

Là encore, trois rechercher / remplacer à base d'expressions régulières et le tour est joué.

  1. Supprimer la ligne vide après un commentaire, qu'il s'agisse d'un commentaire simple // xxxx (SA1512) ou d'un commentaire d'en-tête de la forme /// xxxx (SA1506) :
  • Rechercher : //{.*}\n\n
  • Remplacer par : //\1\n
  1. Supprimer la ligne vide après une accolade ouvrante (SA1505) :


* Rechercher : \{\n\n

  • Remplacer par : {\n


# Supprimer la ligne vide avant une accolade fermante (SA1508) :

  • Rechercher : \n\n\}
  • Remplacer par : \n}

Et voilà le résultat !

------ StyleCop started ------

Pass 1: AssemblyInfo.cs...
Pass 1: BDError.cs...
Pass 1: BDHelper.cs...

---------------------- Done ----------------------

Violation Count: 0

lundi 29 septembre 2008

Répéter un formulaire PDF sur plusieurs pages

Pour parvenir à faire un état sur plusieurs pages en remplissant plusieurs fois le même formulaire PDF avec des données différentes, je n'ai finalement pas eu trop à me creuser la tête. J'ai simplement interrogé le tout nouveau site Stack Overflow qui m'a gentiment expliqué How do I programmatically create a PDF in my .NET application?

namespace Altrr.iText {

    using System;
    using System.Collections;
    using System.IO;
    using iTextSharp.text;
    using iTextSharp.text.pdf;

    /// <summary>
    /// Tests du composant iTextSharp
    /// </summary>
    public class Start {

        /// <summary>
        /// Point d'entrée principal de l'application.
        /// </summary>
        [STAThread]
        static void Main (string[] args) {

            string pdfSource = @"D:\Altrr\iText\register_form1.pdf";
            if (args.Length == 1) {
                pdfSource = args[0];
            }
            FillFields(pdfSource);

        }

        /// <summary>
        /// Remplis les différents champs d'un formulaire PDF
        /// </summary>
        static void FillFields (string pdfSource) {

            // Création d'un objet PDF Reader basé sur le formulaire PDF
            Console.WriteLine(pdfSource + " :");

            string pdfRempli = pdfSource.Replace(".pdf", "_test.pdf");
            Document doc = new Document();
            PdfCopy copy = new PdfCopy(doc, new FileStream(pdfRempli, FileMode.Create));
            doc.Open();

            for (int i = 0; i < 10; i++) {

                PdfReader pdfReader = new PdfReader(pdfSource);

                MemoryStream temp = new MemoryStream();
                PdfStamper pdfStamper = new PdfStamper(pdfReader, temp);

                AcroFields fields = pdfStamper.AcroFields;
                fields.SetField("person.name", i.ToString() + "Laura Specimen");
                fields.SetField("person.address", i.ToString() + "Paulo Soares Way 1");
                fields.SetField("person.postal_code", "F00b4R", "FOOBAR");
                fields.SetField("person.email", i.ToString() + "laura@lowagie.com");
                fields.SetField("person.programming", "JAVA");
                fields.SetField("person.language", "FR");
                fields.SetField("person.preferred", "EN");
                fields.SetField("person.knowledge.English", "On");
                fields.SetField("person.knowledge.French", "On");
                fields.SetField("person.knowledge.Dutch", "Off");

                pdfStamper.FormFlattening = true;
                pdfStamper.Close();

                PdfReader tempReader = new PdfReader(temp.ToArray());

                copy.AddPage(copy.GetImportedPage(tempReader, pdfReader.NumberOfPages));
                copy.FreeReader(tempReader);
            }
            doc.Close();
        }

    }
}

Pour que ça marche, j'ai dû ajouter un using iTextSharp.text; sans quoi le type 'Document' n'est pas référencé.

Et j'utilise aussi pdfStamper.FormFlattening = true; pour que le formulaire PDF ne soit plus modifiable. En fait, cela fait que le fichier PDF final redevient un simple PDF sans plus aucun champ saisissable.

Pour l'instant, je vais déjà utiliser ça comme ça parce que l'échéance pour la mise à jour de l'état approche à grands pas. Puis quand j'aurais fini j'essaierai de comprendre un peu mieux ce qui se passe et comment ça marche.

vendredi 26 septembre 2008

Remplir les champs d'un formulaire PDF

Après avoir réussi à lister les champs d'un formulaire PDF, ce coup-ci je fais un premier essai pour mettre des données dans mon formulaire. Pour commencer, je me contente d'y mettre un compteur numérique, ce qui va me permettre de repérer chaque champ dans le formulaire.

namespace Altrr.iText {

    using System;
    using System.Collections;
    using System.IO;
    using iTextSharp.text.pdf;

    /// <summary>
    /// Tests du composant iTextSharp
    /// </summary>
    public class Start {

        /// <summary>
        /// Point d'entrée principal de l'application.
        /// </summary>
        [STAThread]
        static void Main (string[] args) {

            string pdfSource = @"D:\Altrr\iText\register_form1.pdf";
            if (args.Length == 1) {
                pdfSource = args[0];
            }
            FillFields(pdfSource);

        }

        /// <summary>
        /// Remplis les différents champs d'un formulaire PDF
        /// </summary>
        static void FillFields (string pdfSource) {

            // Création d'un objet PDF Reader basé sur le formulaire PDF
            Console.WriteLine(pdfSource + " :");
            PdfReader pdfReader = new PdfReader(pdfSource);

            // Création d'un objet PDF Stamper à partir du formulaire PDF
            string pdfRempli = pdfSource.Replace(".pdf", "_test.pdf");
            PdfStamper pdfStamper = new PdfStamper(pdfReader, new FileStream(pdfRempli, FileMode.Create));
            AcroFields fields = pdfStamper.AcroFields;

            // Boucle pour remplir les différents champs
            int i = 0;
            foreach (DictionaryEntry field in fields.Fields) {
                // nom du champ
                string key = field.Key.ToString();
                // type du champ (et selon le cas liste des valeurs possibles)
                string type = "";
                i++;
                string data = i.ToString();
                string[] list = null;
                switch (fields.GetFieldType(key)) {
                    case AcroFields.FIELD_TYPE_CHECKBOX:
                        type = "CheckBox";
                        list = fields.GetAppearanceStates(key);
                        break;
                    case AcroFields.FIELD_TYPE_COMBO:
                        type = "Combo";
                        list = fields.GetListOptionExport(key);
                        break;
                    case AcroFields.FIELD_TYPE_LIST:
                        type = "List";
                        list = fields.GetListOptionExport(key);
                        break;
                    case AcroFields.FIELD_TYPE_NONE:
                        type = "None";
                        break;
                    case AcroFields.FIELD_TYPE_PUSHBUTTON:
                        type = "PushButton";
                        break;
                    case AcroFields.FIELD_TYPE_RADIOBUTTON:
                        type = "RadioButton";
                        list = fields.GetAppearanceStates(key);
                        break;
                    case AcroFields.FIELD_TYPE_SIGNATURE:
                        type = "Signature";
                        break;
                    case AcroFields.FIELD_TYPE_TEXT:
                        type = "Text";
                        break;
                }
                if (list != null) {
                    data = list[list.Length - 1];
                }
                Console.WriteLine("- " + key + " : " + data);
                fields.SetField(key, data);
            }
            // Fermeture du formulaire PDF rempli
            pdfStamper.Close();
        }

    }
}

Maintenant, il me reste à trouver comment éviter de créer physiquement un nouveau fichier à chaque fois. Etant donné que c'est destiné à être utilisé dans une application ASP.NET, je préfèrerais trouver une méthode qui me permette de renvoyer directement le résultat vers le poste client. Normalement, c'est le genre de chose qui devrait se trouver facilement sur Google (à condition que cela soit réalisable).

Et ce qui serait bien, c'est de voir s'il est possible à partir du formulaire modèle de remplir plusieurs pages avec des données différentes données. De cette façon, je pourrais gérer des impressions groupées de plusieurs documents à la fois.

jeudi 25 septembre 2008

Lister les champs d'un formulaire PDF

Aujourd'hui, j'ai eu besoin de re-faire un état Crystal Report. Il s'agit d'un document officiel dont le format a changé du jour au lendemain et qui doit être en production pour le 1° février. Vu les délais et surtout étant donné que maintenant plus personne ne maitrise Crystal Report chez nous, ça risquait d'être un peu juste.

Mais heureusement, l'organisme qui demande ce nouvel état a eu la bonne idée de le fournir sous forme de formulaire PDF prêt à remplir. Comme ce n'est normalement pas un état trop demandé (ce qui fait qu'on n'a pas vraiment besoin de la puissance de feu de Crystal Report), j'en ai profité pour tester s'il était possible de remplir ce formulaire depuis ASP.NET.

Apparemment, la meilleure solution bon marché pour cela semble être iTextSharp. C'est gratuit et ça semble vraiment très simple à utiliser. Sauf en ce qui concerne la mise à jour des formulaires PDF où il m'a fallu beaucoup creuser pour réussir à trouver des exemples concrets :

Une fois tout ça trouvé, ça a de suite été plus facile de faire un premier essai qui marche :

namespace Altrr.iText {

    using System;
    using System.Collections;
    using iTextSharp.text.pdf;

    /// <summary>
    /// Tests du composant iTextSharp
    /// </summary>
    public class Start {

        /// <summary>
        /// Point d'entrée principal de l'application.
        /// </summary>
        [STAThread]
        static void Main (string[] args) {

            string pdfSource = @"D:\Altrr\iText\register_form1.pdf";
            if (args.Length == 1) {
                pdfSource = args[0];
            }
            ListFieldNames(pdfSource);

        }

        /// <summary>
        /// Affiche la liste des champs d'un formulaire PDF
        /// </summary>
        static void ListFieldNames (string pdfSource) {

            // Création d'un objet PDF Reader basé sur le formulaire PDF
            Console.WriteLine(pdfSource + " :");
            PdfReader pdfReader = new PdfReader(pdfSource);
            AcroFields fields = pdfReader.AcroFields;

            // Boucle sur les différents champs du formulaire
            foreach (DictionaryEntry field in fields.Fields) {
                // nom du champ
                string key = field.Key.ToString();
                // type du champ (et selon le cas liste des valeurs possibles)
                string type = "";
                string data = "";
                switch (fields.GetFieldType(key)) {
                    case AcroFields.FIELD_TYPE_CHECKBOX:
                        type = "CheckBox";
                        data = String.Join(", ", fields.GetAppearanceStates(key));
                        break;
                    case AcroFields.FIELD_TYPE_COMBO:
                        type = "Combo";
                        data = String.Join(", ", fields.GetListOptionExport(key));
                        break;
                    case AcroFields.FIELD_TYPE_LIST:
                        type = "List";
                        data = String.Join(", ", fields.GetListOptionExport(key));
                        break;
                    case AcroFields.FIELD_TYPE_NONE:
                        type = "None";
                        break;
                    case AcroFields.FIELD_TYPE_PUSHBUTTON:
                        type = "PushButton";
                        break;
                    case AcroFields.FIELD_TYPE_RADIOBUTTON:
                        type = "RadioButton";
                        data = String.Join(", ", fields.GetAppearanceStates(key));
                        break;
                    case AcroFields.FIELD_TYPE_SIGNATURE:
                        type = "Signature";
                        break;
                    case AcroFields.FIELD_TYPE_TEXT:
                        type = "Text";
                        break;
                }
                Console.Write("- " + key + " : " + type);
                if (data != "") {
                    Console.WriteLine(" (valeurs possibles : " + data + ")");
                } else {
                    Console.WriteLine("");
                }
            }
        }

    }
}

Ce qui avec le fichier d'exemple me donne :

D:\Altrr\iText\register_form1.pdf :
- person.name : Text
- person.postal_code : Text
- person.knowledge.Dutch : CheckBox (valeurs possibles : Off, On)
- person.language : Combo (valeurs possibles : EN, FR, NL)
- person.knowledge.French : CheckBox (valeurs possibles : Off, On)
- person.programming : List (valeurs possibles : JAVA, C, CS, VB)
- person.email : Text
- person.address : Text
- person.preferred : RadioButton (valeurs possibles : Off, NL, EN, FR)
- person.knowledge.English : CheckBox (valeurs possibles : Off, On)

Il y a quand même un problème, parce qu'avec mon formulaire PDF, j'obtiens une liste des champs totalement dans le désordre, c'est à dire ni dans l'ordre de saisie, ni même dans l'ordre alphabétique. Et comme les noms des champs sont moyennement clairs, c'est assez galère pour savoir à quoi correspond chaque champ.

Ca vaudrait peut être le coup d'essayer la fonction pdfReader.AcroFields.GetFieldPositions(key) qui renvoie la position du champ pour essayer de trier cette liste de haut en bas et de gauche à droite. Ou alors, simplement remplir chaque champ avec une valeur numérique croissante et voir ce que ça donne.

mercredi 17 septembre 2008

Tableau d'ArrayList

Pour créer un tableau de 2 ArrayList, il ne suffit pas d'écrire :

ArrayList[] list = new ArrayList[2];

Cela ne fait que déclarer un tableau de 2 ArrayList mais qui sont nulls. Il faut aussi initialiser chacune des ArrayList en procédant de la façon suivante :

ArrayList[] list = new ArrayList[2] { new ArrayList(), new ArrayList() };

C'est pas plus compliqué que ça :)

Et dans le cas où on aurait besoin d'un peu plus que 2 ArrayList, il vaut mieux passer par une boucle :

ArrayList[] list = new ArrayList[2];
for (int i = 0; i < list.Length; i++) {
    list[i] = new ArrayList();
}

mardi 15 juillet 2008

Créer un fichier Excel en .NET

Mon voisin de bureau avait la désagréable mission de devoir réaliser une interface (lui aussi!) pour exporter des données au format Excel. Sinon y'aurait des têtes qui allaient tomber !

Comme la vie dans un openspace bondé exige une bonne dose de civilité et pour éviter les éclaboussures j'ai fait comme les autres et j'ai donc essayé de le sortir de ce mauvais pas.

Générer de l'Excel

Habituellement, on répond à ce genre de besoin en générant un bête fichier CSV (en tout cas moi c'est ce que je fais à chaque fois) ou dans les cas les plus extrêmes certains d'entre nous utilisent la librairie ExcelXmlWriter pour produire un fichier Excel au format XML.

Mais dans le cas présent, il était nécessaire de fournir un authentique fichier Excel parce que l'application en face s'attendait à lire un "vrai" format Excel bien binaire comme dans le temps, estampillé "Made in Microsoft" (pour un programme en PHP c'est d'ailleurs assez rigolo).

Il ne restait donc que 3 solutions possibles :

  • installer Excel sur le serveur IIS et y aller à coup d'objets COM : sur un serveur de prod! ça va pas la tête ?
  • trouver une librairie qui sache créer de "vrais" fichiers Excel : et pourquoi pas avoir à l'acheter en plus ?
  • vérifier une fois pour toute si le provider OleDb permet aussi d'écrire dans un fichier Excel : ah ouaih ça c'est marrant !

Lire depuis un fichier Excel

Pour commencer, créer vite fait un petit fichier Excel pour se rappeler comment on fait pour lire ce genre de truc :

  • 3 colonnes : Code, Libellé, Date
  • 1° ligne : 1, Un, 01/01/2008
  • 2° ligne : 2, Deux, 02/02/2008
  • 3° ligne : 3, Trois, 03/03/2008

Enregistrer tout ça dans un fichier que l'on nommera Classeur1.xls

Fouiller un peu dans sa mémoire et pas mal sur le disque dur pour retrouver comment coder un petit programme qui va lire le contenu de ce fichier :

using System;
using System.Data.OleDb;

namespace OleDbExcel {

    class Class1 {
        [STAThread]
        static void Main(string[] args) {

            // Défini la chaine de connexion au fichier Excel
            string cnstr = @"Provider=Microsoft.Jet.OLEDB.4.0;Extended Properties=""Excel 8.0;HDR=YES;""";
            cnstr += @";Data Source=D:\Altrr\OleDbExcel\Classeur1.xls";

            // Connexion au fichier Excel
            OleDbConnection cn = new OleDbConnection(cnstr);
            cn.Open();

            // Affiche le contenu
            OleDbCommand cm = cn.CreateCommand();
            cm.CommandText = "SELECT * FROM [Feuil1$]";
            OleDbDataReader dr = cm.ExecuteReader();
            while (dr.Read() == true) {
                Console.WriteLine("{0} : {1}\t{2}", dr[0], dr[1], dr[2]);
            }
            dr.Close();

            // Ferme la connexion
            cn.Close();

            // Fin du test
            Console.WriteLine();
            Console.Write("(Entrée) pour terminer...");
            Console.ReadLine();
        }

    }
}

F5 => ça marche => au suivant!

(une parenthèse pour info : Feuil1 c'est le nom de la 1° feuille dans le classeur Excel et pour l'utiliser en tant que table il faut ajouter un $ au bout et mettre le tout entre crochets)

Ecrire dans un fichier Excel

Et maintenant, le saut dans l'inconnu, à savoir tenter d'écrire dans un fichier Excel via une connexion OleDb (de l'inédit pour moi) :

using System;
using System.Data.OleDb;

namespace OleDbExcel {

    class Class1 {
        [STAThread]
        static void Main(string[] args) {

            // Défini la chaine de connexion au fichier Excel
            string cnstr = @"Provider=Microsoft.Jet.OLEDB.4.0;Extended Properties=""Excel 8.0;HDR=YES;""";
            cnstr += @";Data Source=D:\Altrr\OleDbExcel\Classeur1.xls";

            // Connexion au fichier Excel
            OleDbConnection cn = new OleDbConnection(cnstr);
            cn.Open();

            // Création d'un objet OleDbCommand
            OleDbCommand cm = cn.CreateCommand();

            // Insère une 4° ligne dans le fichier Excel
            cm.CommandText = "INSERT INTO [Feuil1$] ([Code], [Libellé], [Date]) VALUES (4, 'Quatre', '04/04/2008')";
            cm.ExecuteNonQuery();

            // Insère une 5° ligne dans le fichier Excel
            cm.CommandText = "INSERT INTO [Feuil1$] ([Code], [Libellé], [Date]) VALUES (5, 'Cinq', '05/05/2008')";
            cm.ExecuteNonQuery();

            // Affiche le contenu
            cm.CommandText = "SELECT * FROM [Feuil1$]";
            OleDbDataReader dr = cm.ExecuteReader();
            while (dr.Read() == true) {
                Console.WriteLine("{0} : {1}\t{2}", dr[0], dr[1], dr[2]);
            }
            dr.Close();

            // Ferme la connexion
            cn.Close();

            // Fin du test
            Console.WriteLine();
            Console.Write("(Entrée) pour terminer...");
            Console.ReadLine();
        }

    }
}

F5 => ça marche aussi !

On tente la même chose sur le serveur (de prod ! quand y'a des têtes en jeux on a vraiment plus peur de rien) et ça marche encore !!!

Que demander de plus ?

On peut même utiliser des paramètres au lieu de commandes SQL en "dur"

using System;
using System.Data.OleDb;

namespace OleDbExcel {

    class Class1 {
        [STAThread]
        static void Main(string[] args) {

            // Défini la chaine de connexion au fichier Excel
            string cnstr = @"Provider=Microsoft.Jet.OLEDB.4.0;Extended Properties=""Excel 8.0;HDR=YES;""";
            cnstr += @";Data Source=D:\Altrr\OleDbExcel\Classeur1.xls";

            // Connexion au fichier Excel
            OleDbConnection cn = new OleDbConnection(cnstr);
            cn.Open();

            // Création d'un objet OleDbCommand
            OleDbCommand cm = cn.CreateCommand();

            // Insère une 6° ligne dans le fichier Excel
            cm.CommandText = "INSERT INTO [Feuil1$] ([Code], [Libellé], [Date]) VALUES (@Code, @Libelle, @Date)";
            cm.Parameters.Add("@Code", 6);
            cm.Parameters.Add("@Libelle", "Six");
            cm.Parameters.Add("@Date", new DateTime(2008, 6, 6));
            cm.ExecuteNonQuery();

            // Affiche le contenu
            cm.CommandText = "SELECT * FROM [Feuil1$]";
            OleDbDataReader dr = cm.ExecuteReader();
            while (dr.Read() == true) {
                Console.WriteLine("{0} : {1}\t{2}", dr[0], dr[1], dr[2]);
            }
            dr.Close();

            // Ferme la connexion
            cn.Close();

            // Fin du test
            Console.WriteLine();
            Console.Write("(Entrée) pour terminer...");
            Console.ReadLine();
        }

    }
}

On ne peut pas faire un DELETE FROM [Feuil1$] pour vider le fichier avant d'y insérer de nouvelles données : pas très utile donc pas très grave.

Ce qui est un peu plus embêtant, c'est qu'il ne semble pas possible de partir d'un fichier Excel vide et d'y "créer" dynamiquement les colonnes que l'on veut

  • faire un 1° "INSERT INTO [Feuil1$] ([Code], [Caption], [Date]) VALUES ('Code', 'Libellé', 'Date')" plante
  • commencer par un "CREATE TABLE [Feuil1$] ..." plante aussi

Fin de la récréation

C'est ennuyeux, mais comme cela n'a strictement aucune importante dans le cas qui nous occupait au départ, on ne va pas y passer plus que la pause de midi.

mercredi 11 juillet 2007

Faire un Join sur un ArrayList

string texts = string.Join(",", (string[]) myArrayList.ToArray(typeof(string)));;

mercredi 13 décembre 2006

Compacter une base de données Microsoft Access à l'aide de C#

Adaptation de la KB306287 :

  1. Ouverture d'une nouvelle application de console C#.
  2. Dans la fenêtre Explorateur de solutions, cliquez avec le bouton droit sur le nœud Références, puis sélectionnez Ajouter une référence.
  3. Dans la boîte de dialogue Ajouter une référence, cliquez sur l'onglet COM, puis sélectionnez Microsoft Jet and Replication Objects 2.6 Library. Cliquez sur Sélectionner pour l'ajouter à la liste Composants sélectionnés. Cliquez sur OK.
  4. Un avertissement s'affiche si aucun wrapper ne figure dans la bibliothèque sélectionnée. Cliquez sur Oui pour générer un wrapper. La bibliothèque Microsoft ActiveX Data Objects (ADODB) et les références JRO s'ajoutent aux références du projet.
  5. Dans la fenêtre de l'Explorateur de solutions, cliquez avec le bouton droit sur Class1.cs, puis cliquez sur Afficher le code.
  6. Supprimez tout le code de la fenêtre.
  7. Copiez le code suivant et collez-le dans la fenêtre de code :
    using System;
    using System.IO;
    
    namespace CompactMDB {
      class Class1 {
        [STAThread]
        static void Main(string[] args) {
          // http://support.microsoft.com/kb/306287/
          if (args.Length != 1) {
            Console.WriteLine(@"CompactMDB D:\path\database.mdb");
          } else {
            string dbName = args[0].Trim();
            string dbTemp = dbName + "_pak";
            string provider = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=";
            try {
              JRO.JetEngine jet = new JRO.JetEngine();
              jet.CompactDatabase(provider + dbName, provider + dbTemp);
            } catch (Exception ex) {
              Console.WriteLine(ex.Message);
              dbName = "";
            }
            if (dbName != "") {
              try {
                FileInfo fi = new FileInfo(dbName);
                string result = dbName + " : " + fi.Length.ToString();
                File.Delete(dbName);
                File.Move(dbTemp, dbName);
                fi.Refresh();
                result += " -> " + fi.Length.ToString();
                Console.WriteLine(result);
              } catch (Exception ex) {
                Console.WriteLine(ex.Message);
              }
            }
          }
        }
      }
    }
    
  8. Régénérez la solution.

Le programme s'exécute ensuite à partir d'une Invite de commande, en tapant : CompactMDB D:\path\database.mdb.

vendredi 4 novembre 2005

Retrouver un numéro de semaine

public static int GetWeekNumber (DateTime dt) {
  CultureInfo culture = CultureInfo.CurrentCulture;
  int intWeek = culture.Calendar.GetWeekOfYear(dt, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
  return intWeek;
}

(publié à l’origine sur http://www.bigbold.com/snippets/posts/show/856)

jeudi 20 octobre 2005

Convertir un type String en Enum

enum EngineType {
  unknow,
  access,
  db2,
  mysql,
  odbc,
  oledb,
  oracle,
  postgre,
  sqlserver
}
string cnxTypeString = "mysql";
EngineType cnxTypeEngine = EngineType.unknow;
if (Enum.IsDefined(typeof(EngineType), cnxTypeString)) {
  cnxTypeEngine = (EngineType) Enum.Parse(typeof(EngineType), cnxTypeString, true);
}

(publié à l'origine sur http://www.bigbold.com/snippets/posts/show/824)

mardi 27 septembre 2005

Algorithme pour calculer Pâques

/// <summary>
/// Algorithm for calculating the date of Easter Sunday
/// (Meeus/Jones/Butcher Gregorian algorithm)
/// http://en.wikipedia.org/wiki/Computus#Meeus.2FJones.2FButcher_Gregorian_algorithm
/// </summary>
/// A valid Gregorian year
/// Easter Sunday
public static DateTime EasterDate(int year) {
    int Y = year;
    int a = Y % 19;
    int b = Y / 100;
    int c = Y % 100;
    int d = b / 4;
    int e = b % 4;
    int f = (b + 8) / 25;
    int g = (b - f + 1) / 3;
    int h = (19 * a + b - d - g + 15) % 30;
    int i = c / 4;
    int k = c % 4;
    int L = (32 + 2 * e + 2 * i - h - k) % 7;
    int m = (a + 11 * h + 22 * L) / 451;
    int month = (h + L - 7 * m + 114) / 31;
    int day = ((h + L - 7 * m + 114) % 31) + 1;
    DateTime dt = new DateTime(year, month, day);
    return dt;
}
  • Lundi de Pâques : Easter Monday = Easter Sunday + 1
  • Jeudi de l’Ascension : Ascension Day = Easter Sunday + 39
  • Dimanche de Pentecôte : Pentecost Sunday = Easter Sunday + 49
  • Lundi de Pentecôte : Pentecost Monday = Easter Sunday + 50

(publié à l'origine sur http://www.bigbold.com/snippets/posts/show/765)

mercredi 14 septembre 2005

Capitalization

D'après C# Regular Expressions :

using System.Text.RegularExpressions;

public class MyClass {

  public static void Main() {
    string text = "the quick red fox jumped over the lazy brown DOG.";
    System.Console.WriteLine("text=[" + text + "]");
    string result = Regex.Replace(text, @"w+", new MatchEvaluator(MyClass.CapText));
    System.Console.WriteLine("result=[" + result + "]");
    System.Console.ReadLine();
  }

  static string CapText(Match m) {
    string temp = m.ToString();
    temp = char.ToUpper(temp[0]) + temp.Substring(1, temp.Length - 1).ToLower();
    return temp;
  }

}

(publié à l'origine sur http://www.bigbold.com/snippets/posts/show/705)

Edit : commentaire de utagger :

Here's a shorter version:

protected void Button1_Click(object sender, EventArgs e) {
  Label1.Text = Regex.Replace(TextBox1.Text, @”\b\w”, new MatchEvaluator(stam));
}

protected string stam(Match m) {
  return m.Value.ToUpper();
}

(the trick is using \b which is a 0-length match of word boundaries, including ^ and \s)

mardi 2 août 2005

(Wdevs) A C# Client to the blogmarks.net Atom API

Blogmarks.net is a free & open bookmarks manager based on keywords (aka Tags) and sharing. With Blogmarks.net you'll be able to store and share with other users your favorite websites trough a "blog-like" technology. Your bookmarks will now be available from any internet connection and accessible from a variety of others services trough our API.

I just upload a c# client in the developper wiki. It was a great exercise to make REST with c#.

- page 1 de 2