17 avril 2014

C# : Requête SQL qui remonte du XML

Si vous voulez pouvoir utiliser le générateur de XML de SQL Server directement en C#, je vous propose cette petite procédure qui permet exécuter le SQL et de retourner directement un XElement.

    public XElement ExecuteRequeteGetXml(string sql, params SqlParameter[] commandParameters)
    {
      StringBuilder res = new StringBuilder();
      SqlCommand cmd = this.GetSqlCommand(CommandType.Text, sql, commandParameters);
      using (XmlReader xmlr  = cmd.ExecuteXmlReader())
      {
        xmlr.Read();
        while (xmlr.ReadState != System.Xml.ReadState.EndOfFile)
        {
          res.Append(xmlr.ReadOuterXml());
        }
      }

      if (res.Length > 0)
      {
        return XElement.Parse(res.ToString());
      }
      else
      {
        return null;
      }
    }

On récupère le reader, puis on lit jusqu’à la fin du flux XML et on recopie l'info dans un StringBuilder qui est parsé en XElement à la fin.
Utile aussi la procédure GetSqlCommand : Qui suppose que la chaîne de connexion est dans le fichier de configuration avec la clé "Base".

    private SqlCommand GetSqlCommand(CommandType typeCommande, string requeteSql, params SqlParameter[] commandParameters)
    {
      SqlCommand command = new SqlCommand();
      string connexionString = ConfigurationManager.ConnectionStrings["Base"].ConnectionString;
      SqlConnection connexion = new SqlConnection(connexionString);
      connexion.Open();
      command.Connection = connexion;
      command.CommandText = requeteSql;
      command.CommandType = typeCommande;

      if (commandParameters != null)
      { // les paramètres de la commande
        foreach (SqlParameter p in commandParameters)
        {
          if (p != null)
          { // Check for derived output value with no value assigned
            if ((p.Direction == ParameterDirection.InputOutput || p.Direction == ParameterDirection.Input) && p.Value == null)
            {
              p.Value = DBNull.Value;
            }

            command.Parameters.Add(p);
          }
        }
      }

      return command;
    }


08 avril 2014

Redis : Managment Studio publié sur GitHub

Après la librairie, l'application...

https://github.com/slewden/RedisManagementStudio

Reste à faire évoluer pour :
S'adapter aux nouveautés 2.8 (cluster, nouvelles instructions).
Corriger certains petits bugs lorsque les clé expirent notamment
Terminer les points de suivis et mettre en place un moniteur en tâche de fond.


Any suggestions ?

Enjoy


04 avril 2014

Redis: Librairie d'accès à Redis publié dans GitHub

Voila les sources c# du premier jet de ma librairie d'accès au serveur REDIS.

La librairie est développée sur le jeu d'instructions de la version 2.6 du serveur . Les nouveautés des versions ultérieures ne sont donc pas encore disponibles !

Voici le lien
https://github.com/slewden/ClientRedisLib

Chaque méthode a été testée de manière unitaire, mais il reste des points noir (déjà) identifiés au fil des tentatives d'utilisation :

  • Pas de multitâche possible car pas de gestion de pool de connexion (le driver s'emmêle les pinceaux !)
  • La perte de connexion est mal gérée (et donc à revoir !!)

Enjoy

11 mars 2014

SQL : Gestion d'erreur

Un petit exemple de gestion d'erreur dans SQL SERVER quand on a des procédures qui appellent des procédures !
Deux procédures pour l'exemple :

CREATE PROCEDURE PLANTE
AS
BEGIN
 PRINT 'Je plante de temps en temps...';
 IF (RAND() > 0.6)
   PRINT 1/0;
 ELSE
   PRINT 'Pas cette fois !';
END
;

CREATE PROCEDURE Test_Erreur
AS
BEGIN
  BEGIN TRY
    PRINT 'Travail en cours...';
    EXEC PLANTE;
    IF (RAND() > 0.6)
      PRINT 1/0;
    ELSE
      PRINT 'Pas cette fois !';
    END
    PRINT 'Fini !';
  END TRY
  BEGIN CATCH
   DECLARE @msg VARCHAR(MAX), @proc VARCHAR(200);
   DECLARE @sev INT, @state INT;
   SET @proc = ERROR_PROCEDURE();

   SELECT @msg = 'Erreur' + CASE WHEN @proc = 'Test_Erreur' THEN '' ELSE ' dans ' + @PROC END + ' Ligne ' + CONVERT(VARCHAR(10), ERROR_LINE()) + ' : ' + ERROR_MESSAGE()
     , @sev = ERROR_SEVERITY(), @state = ERROR_STATE();

   RAISERROR (@msg, @sev, @state);
  END CATCH

END

Lorsqu'on lance la procédure Test_Erreur, Si erreur à lieu, le message indique s'il a eu lieu dans la procédure Test_Erreur ou dans PLANTE.

Les ingrédients intéressants :

  1. BEGIN TRY / END TRY / BEGIN CATCH / END CATCH : Qui permettent de déclarer un bloc de code qui sera en mesure d’attraper les erreurs.
  2. Le contenu du bloc Catch : Qui construit un message personnalisé en fonction d'ou est apparue l'erreur :
Notez l'utilisation des procédures :
  •  ERROR_PROCEDURE() Qui renvoie le nom de la procédure en erreur.
  •  ERROR_LINE() : Qui renvoie le numéro de la ligne qui a généré l'erreur dans la procédure.
  •  ERROR_MESSAGE() : Qui renvoie le message d'erreur
  •  ERROR_SEVERITY() : Qui renvoie le niveau ce criticité de l'erreur
  •  ERROR_STATE() : Qui renvoie le statut de l'erreur.

L'erreur est ensuite propagée à l'extérieur avec un RAISERROR qui reprend les informations de l'erreur originale (sévérité et statut) et un message adaptée à l'erreur.


31 janvier 2014

SQL : Identity dans les tables temporaires

Une Astuce de DOMINIQUE : que je reprend ici en "l'état"


Désolé pour ceux qui peuvent connaitre, mais je viens de le découvrir et ça me manquait notamment pour bosser avec des tables temporaires...

Problème :

  Lors de la création d'un table par
SELECT (...) INTO #foo

  #foo n'a pas d'id créé automatiquement.

Solution :

 SELECT IDENTITY(INT, 1, 1) AS foo_id, (...) INTO #foo

c# Améliorer la visualisation d'une classe dans le debugger

Afin d'améliorer la vision d'un objet dans le débogueur VISUAL STUDIO, il existe un attribut qui peut s'avérer bien pratique : DebuggerDisplay qui prend en paramètre une chaîne de formatage des propriétés de l'objet à afficher.

Exemple :
  [DebuggerDisplay("Rubrique ={Code} = {Valeur}")]
  public class FicheSimple
  {
    /// <summary>
    /// Initialise une nouvelle instance de la classe <see cref="FicheSimple"/>
    /// </summary>
    /// <param name="rub">Le code de la rubrique</param>
    public FicheSimple(string rub)
    {
      this.Code = rub.ToLower();
      this.Valeur = "";
    }

    #region Properties
    /// <summary>
    /// Le code de la rubrique (forcé en minuscule)
    /// </summary>
    public string Code { get; private set; }

    /// <summary>
    /// La valeur mémorisée
    /// </summary>
    public string Valeur { get; set; }
    #endregion
  }

La chaîne de format, prend entre accolades les propriétés à afficher.
Cela peut être pratique pour certain débogage un peu longs.

Ah : Penser à ajouter le bon using : using System.Diagnostics;


Voir la doc pour plus de détails ;-)

Enjoy

16 janvier 2014

Afficher en mode console un compteurs de progression

Pour debugger des programmes de type Batch, on utilise souvent la console. Quand on veut savoir ou en est un programme cela peut être utile. Quand ce programme boucle, afficher la progression ligne à ligne devient vite illisible.

Voici un exemple c# de code pour mettre en place un compteur de type : Traitement en cours 44 sur 123 et de faire bouger la valeur sans "polluer" l'affichage de la console.

Tout l'intérêt du truc est dans la méthode et les propriétés suivantes de l'objet Console :

  •    Console.SetCursorPosition(x, y) : Place le curseur en X, Y sur la console
  •   Console.CursorLeft : Renvoie la position X du curseur de la console
  •   Console.CursorTop : Renvoie la position Y du curseur de la console

Pour que cela fonctionne bien il faut s'assurer que la console ne scrolle pas = que l'affichage ne dépasse pas le nombre de lignes du buffer de la console. Dans ce cas, une solution est d'effacer la console avant d'utiliser le truc. Avec ce bout de code le tour est joué :
if (Console.CursorTop > Console.BufferHeight)
{
  Console.Clear();
}


Enjoy

 using System;

namespace ConsoleApplication1
{
  /// <summary>
  /// Programme de démo : pour afficher en mode console un ou plusieurs compteurs
  /// </summary>
  public class Program
  {
    /// <summary>
    /// Point d'entré du programme
    /// </summary>
    /// <param name="args">Les arguments de la ligne de commande</param>
    public static void Main(string[] args)
    {
      int max = 100000;
      Console.WriteLine("Programme de tests : pour afficher en mode console un ou plusieurs compteurs");
      Console.WriteLine();
      InfoCompteur pt = PrintCompteur("En cours : ", 0, max);
      Console.WriteLine();
      InfoCompteur pt2 = PrintCompteur("Traité : ", 0, max);
      Console.WriteLine();

      for (int i = 1; i <= max; i++)
      {
        PrintXY(pt, i);
        PrintXY(pt2, i);
       
        if ((i % 5000) == 0)
        { // Afficher du texte arbitrairement tout au long de la boucle
          Console.WriteLine("Coucou " + i.ToString());
        }
      }

      Console.WriteLine("--- Appuyez sur une touche pour quitter ---");
      Console.ReadKey();
    }

    /// <summary>
    /// Démarre un compteur de type : [txt] xxx sur yyy
    /// </summary>
    /// <param name="txt">Texte à afficher avant le compteur</param>
    /// <param name="value">Valeur initiale du compteur</param>
    /// <param name="count">Nombre d'éléments total</param>
    /// <returns>Les informations sur le compteur en cours à utiliser pour les prochains appels</returns>
    private static InfoCompteur PrintCompteur(string txt, int value, int count)
    {
      Console.Write(txt);
      int x = Console.CursorLeft;
      int y = Console.CursorTop;
      int le = count.ToString().Length;
      string fmt = string.Format("{{0,{0}}}", le);
      Console.Write(fmt, value);
      Console.WriteLine(" sur " + count.ToString());
      return new InfoCompteur(x, y, le);
    }

    /// <summary>
    /// Continue = affiche une nouvelle valeur pour un compteur de type : [txt] xxx sur yyy
    /// </summary>
    /// <param name="p">Les informations sur le compteur en cours</param>
    /// <param name="value">La nouvelle valeur</param>
    private static void PrintXY(InfoCompteur p, int value)
    {
      int x = Console.CursorLeft;
      int y = Console.CursorTop;

      Console.SetCursorPosition(p.X, p.Y);
      string fmt = string.Format("{{0,{0}}}", p.Size);
      Console.WriteLine(fmt, value);
      Console.SetCursorPosition(x, y);
    }

    /// <summary>
    /// Infos sur un compteur
    /// </summary>
    private class InfoCompteur
    {
      /// <summary>
      /// Initialise une nouvelle instance de la classe <see cref="InfoCompteur" />.
      /// </summary>
      /// <param name="x">Coordonnée X du curseur</param>
      /// <param name="y">Coordonnée Y du curseur</param>
      /// <param name="size">Taille du champ compteur</param>
      public InfoCompteur(int x, int y, int size)
      {
        this.X = x;
        this.Y = y;
        this.Size = size;
      }

      /// <summary>
      /// Coordonnée X du curseur
      /// </summary>
      public int X { get; set; }

      /// <summary>
      /// Coordonnée Y du curseur
      /// </summary>
      public int Y { get; set; }

      /// <summary>
      /// Taille du champ compteur
      /// </summary>
      public int Size { get; set; }
    }
  }
}