SqlDependency, vue rapide.

by Nicolas Calvi 14. octobre 2011 11:54

Il m’a été amené récemment à utiliser les SqlDependency dans le cadre d’un de mes projets. Je vous fais donc partager aujourd’hui mon retour sur cette fonctionnalité SQL Server / .Net et en bonus je vous donne un code pour les gérer plus facilement.

Qu’est que SqlDependecy

Avant toute chose, revenons sur cette fonctionnalité. Depuis SQL Server 2005, il est possible de créer une dépendance entre du code .Net (via les APIs ADO.Net) et un jeu de résultat SQL. Pour faire simple, SQL Server va pourvoir nous notifier quand le résultat d’une requête change. Pour cela on lui fournit une requête SELECT simple qu’il va exécuter, le résultat de cette requête servira de référence pour la notification. Ensuite, si ce même SELECT donne un résultat différent plus tard, SQL Server nous rappelle pour nous notifier que le premier résultat obtenu est différent du dernier résultat qu’il a évalué. Très pratique en web pour faire de l’invalidation de cache.

Requête et base de données

Il y a cependant des choses à connaître quand on utilise la SqlDependecy. Par exemple pour la requête qui est donnée pour référence :

  • La requête qui est donnée pour référence ne doit pas être avec un SELECT *, il lui faut des noms explicites de colonne.
  • La requête qui est donnée pour référence doit indiquer l’utilisateur qui possède la table accédée, par exemple [dbo] pour le cas le plus courant. 

On doit donc avoir des requêtes qui ressemblent à ça : 

SELECT [col1], [col2] FROM [dbo].[MaTable]

Ensuite au niveau de l’instance de la base de données, il faut activer le service BROKER sur la base, si ce n’est pas fait cette commande permettra de l’activer :

ALTER DATABASE [Nom Base] SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE

Le service BROKER va permettre justement de gérer cette communication entre notre assembly et SQL Server. Une fois cette fonctionnalité activée, passons au code coté .Net.

Implémentation de la SqlDependency

Ci-dessous le code pour implémenter simplement une SqlDependency, je vais l’expliquer par la suite.

private void CreateDependency()
{
  string connectionString = "Mettre ici la chaîne de connexion";

  // Démarrage du broker sur la base
  SqlDependency.Start(connectionString);

  // Création de la connexion
  using (SqlConnection connexion = new SqlConnection(connectionString))
  {
    // Création de la commande pour la requêt de référence
    using (SqlCommand command = new SqlCommand("SELECT col1 FROM [dbo].[MaTable]", connexion))
    {
      command.Notification = null;

      // Création de la dépendance
      SqlDependency dependency = new SqlDependency(command);
      dependency.OnChange += new OnChangeEventHandler(DependencyChange);

      // On démarre la dépendance
      command.ExecuteReader();
    }

    // Fermeture de la connexion
    connexion.Close();
  }

  // Arrêt du broker sur la base
  SqlDependency.Stop(connectionString);
}

private void DependencyChange(object sender, SqlNotificationEventArgs e)
{
  // On se désabonne
  (sender as SqlDependency).OnChange -= new OnChangeEventHandler(DependencyChange);

  // Code de traitement ici
}

Ici on peut voir comment se structure la création de la SqlDependency. On doit d’abord démarrer le BROKER de la base, afin qu’il écoute les dépendances, ensuite on exécute une requête ADO.Net classique, à savoir l'instanciation d’une connexion, la création d’une commande et l'exécution de celle-ci.

La création de la dépendance se situe entre la création de la commande et son exécution. On y crée alors la dépendance sur la commande et on s’abonne à l’événement OnChange pour récupérer la notification de changement.

La dépendance n’est créée qu’au moment où la commande est exécutée, le résultat qu’elle produit est la référence SQL Server pour savoir si un changement a été fait.

Ensuite c’est très simple, dès que le résultat de la requête de dépendance change, la fonction de retour (ici DependencyChange) est appelé, il n’y a plus qu’à faire les traitements qui nous intéresses. 

Par contre, il faut bien comprendre une chose, une SqlDependency ne fonctionne qu’une seule fois ! Une fois qu’elle est traitée (en gros que la fonction de retour est invoqué) elle disparait du serveur SQL Server et ne fonctionnera plus. En gros, c’est à usage unique.

Il est donc impossible de créer une SqlDependency qui va invoquer la fonction de retour autant de fois que le résultat de la requête de dépendance est modifié. Pour pouvoir boucler sur les modifications, il faut recréer une autre dépendance.

Dans cette optique, il n’est donc pas nécessaire de conserver les instances des commandes, connexions et autre objets utilisés pour la création de la dépendance. C’est pour cela que dans le code ci-dessus, tout est libéré par des 'using'.

Manager de dépendance

Vous pouvez télécharger plus bas, deux classes qui permettent de gérer des SqlDependency de façon plus automatique et de boucler sur les notifications. J’ai créé ces classes dans le but de factoriser la gestion des SqlDependency par chaîne de connexion. Pour les utiliser, il faut ajouter l’assembly 'System.Configuration', voici le détail des deux classes :

DependencyManagerItem.cs (8,22 kb)

Cette classe gère une dépendance simple, de façon atomique. On peut l’utiliser seule, mais elle n’active pas le BROKER coté base de données. Le constructeur est classique, on y renseigne la chaine de connexion, une fonction de retour et si on active le mode continue (qui recrée la SqlDependency automatiquement).

Cette classe, possède ensuite une propriété 'Datas' qui renvoi l’état du résultat de la requête de référence, ce qui permet de l’avoir sans avoir à le requêter à nouveau. Ensuite, pour démarrer la dépendance il suffit d’invoquer la méthode 'Start()', si vous êtes dans un mode boucle, la méthode 'Stop()' permet d’arrêter la dépendance.

DependencyManager.cs (5,58 kb)

Le principe de cette classe est de gérer de façon plus globale les dépendances. On l’instancie avec une chaine de connexion et elle va démarrer le BROKER de la base, mais aussi l’arrêter quand elle sera recyclée.

Elle possède des propriétés qui lui permettent de démarrer des dépendances avec des valeurs par défaut (il est toujours possible de démarrer une dépendance sans ses valeurs par défaut).

Pour démarrer une dépendance, il faut invoquer la méthode 'Create()' qui prend en paramètre un nom, ce nom servira à identifier la dépendance pendant tout son cycle de vie dans le Manager. Ensuite il y deux versions de 'Create()', une qui crée la dépendance avec les paramètres par défaut, et l’autre pour les outrepasser. Pour finir, une méthode 'Remove()' mettra fin à une dépendance nommée.

Ces classes sont faciles d’utilisation et vous avez le code, donc libre à vous de les étudier et/ou de les adapter à vos besoins.

Dependency Property : Trucs et astuces

by Nicolas Calvi 19. octobre 2010 12:06
Lors des développements en WPF on a souvent besoin d'écrire des propriétés de dépendances pour nos objets. En dehors de la déclaration simple de notre propriété, il y a quelques éléments qui méritent d'être connue. Dans l'exemple qui va suivre, je vais écrire une propriété de dépendance appelée "MaProp", qui sera un Int32 et qui ne peut prendre des valeurs que de 1 à 100.
  
1 - Déclaration de la propriété
 
Il faut bien évidement déclarer sa propriété, dans Visual Studio il y un a snipet pour ça : propdp.
 
public static readonly DependencyProperty MaPropProperty = DependencyProperty.Register("MaProp", typeof(int), typeof(MaClasse), new UIPropertyMetadata(1));
 
Dans les conventions de nommage, on ajoute toujours "Property" sur l'a déclaration statique, ensuite pour initialiser celle-ci, on peut donner une valeur par défaut dans le dernier paramètre, ici 1.
 
2 - Déclaration de l'accesseur
 
Il faut maintenant créer l'accesseur pour cette propriété, ici rien de bien difficile, on doit simplement utiliser le GetValue(DependencyProperty) et le SetValue(DependencyProperty, object). Ces deux fonctions ne font que récupérer ou inscrire des informations dans le dictionnaire de ressource de l'objet, c'est dans celui-ci que les valeurs des propriétés de dépendance sont stockées.
public int MaProp
{
  get
  {
    return ((int)this.GetValue(MaClasse.MaProp));
  }
  set
  {
    this.SetValue(MaProp.MaProp, value);
  }
}
Il est très fortement recommander de ne mettre aucun autre code que celui-ci sur sa propriété. Pour exécuter du code à son changement ou contrôler la valeur, ce sont les deux points suivants.
  
3 - Faire un traitement après modification
 
Pour exécuter du code après un changement de valeur, il est possible de rajouter un Callback dans le constructeur du paramètre UIPropertyMetadata() dans la déclaration statique de la propriété. Ce Callback est de type PropertyChangedCallback. Voici un exemple de déclaration pour cette méthode.
public class MaClasse 
{   
  // Déclaration de la propriété   
  public static readonly DependencyProperty MaPropProperty = DependencyProperty.Register("MaProp", typeof(int), typeof(MaClasse), new UIPropertyMetadata(1, new PropertyChangedCallback(MaClasse.OnMaPropChanged)));    

  // Déclaration de la fonction statique de Callback   
  private static void OnMaPropChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)   
  {    
    // Appel a la fonction interne de modification     
    ((MaClasse)sender).OnMaPropChanged(e.OldValue, e.NewValue);  
  }    
  
  // Déclaration de la fonction interne de modification  
  protected virtual void OnMaPropChanged(int old, int new)   
  {    
     // Votre code ici   
  } 
}
Il est recommandé de déclarer la fonction statique de Callback en privée et la fonction de modification interne en protégée virtuel, ceci permet si on hérite de votre classe, de pouvoir intercepter le changement de valeur de la propriété.
 
4 - Tester la valeur avant modification
Dans notre exemple, on veut pouvoir borner la valeur entre 1 et 100, le premier mécanisme est déjà de l'initialiser dans cet interval, mais il est possible aussi avant l'affectation, de tester la valeur, ceci se passe comme pour le point 3, une fonction de Callback nous permet de la tester, elle s'appelle CoerceValueCallback(). Elle fonctionne comme le PropertyChangedCallback, voici un exemple pour cette méthode
 
public class MaClasse 
{       
  // Déclaration de la propriété       
  public static readonly DependencyProperty MaPropProperty = DependencyProperty.Register("MaProp", typeof(int), typeof(MaClasse), new UIPropertyMetadata(1, new PropertyChangedCallback(MaClasse.OnMaPropChanged), new CoerceValueCallback(MaClasse.CoerceMaProp)));      

  // Déclaration de la fonction statique de Callback  
  private static void OnMaPropChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)  
  {    
    // Appel a la fonction interne de modification    
    ((MaClasse)sender).OnMaPropChanged(e.OldValue, e.NewValue);  
  }    

  // Déclaration de la fonction statique de coerce  
  private static object CoerceMaProp(DependencyObject d, object baseValue)  
  {    
    return(((MaClasse)sender).CoerceMaProp((int)baseValue));  
  }    

  // Déclaration de la fonction interne de modification  
  protected virtual void OnMaPropChanged(int old, int new)  
  {    
    // Votre code ici  
  }    

  // Déclaration de la fonction interne de coerce  
  protected virtual object CoerceMaProp(int value)  
  {    
    // On teste la valeur    
    if (value < 1)      
      return(1);    
    else if (value > 100)      
      return(100);          

    return(value);  
  } 
}
On peut ainsi tester la valeur dans le coerce et la remettre dans un interval souhaité avant son affectation, ce qui déclenchera ensuite le PropertyChangedCallback avec la bonne valeur.

PDC : Future directions for C# and Visual Basic

by Nicolas Calvi 18. novembre 2009 07:55

Pour ma part ma première session a été très intéressante, une présentation sur les concepts et directions que Microsoft va donner a ses langages phares que sont C# et Visual Basic. Outre le fait de montrer quelques évolutions syntaxique c’est surtout l’occasion pour le speaker de préciser qu’à l’avenir les langages seront plus tournés vers le déclaratif à l’instar de Linq que vers l’impératif (même si les deux seront toujours possible).

La première chose mise en avant est que les deux langages seront maintenant identique dans leurs possibilités, fini l’époque ou Visual Basic offrait telle fonctionnalité et C# une autre sans possibilité d’avoir les deux dans le même langage. Avec C# et  Visual Basic 4, ce que peut faire l’un, l’autre peut le faire aussi.

Dans les nouvelles fonctionnalités, une m’a particulièrement attiré, celle concernant le DynamicObject. Le concept est simple, pouvoir accéder a des accesseurs dynamique sur un objet, ce qui était avant impossible car ils étaient déterminés à la compilation. Si vous voulez voir a quoi sa ressemble coté code :  http://msdn.microsoft.com/en-us/library/system.dynamic.dynamicobject%28VS.100%29.aspx

Ce qu’il faut donc retenir de cette session :

·         Plus aucune différence de fonctionnalité.

·         La version déclarative d'un code est plus lisible de la version impérative.

·         L'idée est que le déclaratif est plus facile à mettre en place et plus facile à maintenir, et pose moins de problème de compréhension.

·         Possibilité de créer ses propres fonctions de refactoring de code dans Visual Studio 2010.

 

La session proposait comme exemple la création d’un parser de fichier CSV, en combinant les nouveaux concepts déclaratif de C# 4, on arrivait à un modèle pour le fichier était chargé dans un objet donc les accesseurs était dynamique, et donc que leurs nom changeaient en fonction du fichier CSV chargé.

Convertir une chaîne en Hash MD5

by Nicolas Calvi 16. juillet 2008 15:03

Si certain se pose la question à savoir comment hasher une chaîne de caractère au format MD5, voici la méthode qui le permet. Elle utilise System.Security.Cryptography du framework, ça mange pas de pain et parfois ça dépanne.

using System.Security.Cryptography;

public static string HashToMD5(string p_sChaine)
{
  MD5 oServiceMD5 = null;
  byte[] sChaineMD5 = null;
  StringBuilder oResult = new StringBuilder();

  // Création du provider MD5 et conversion

  oServiceMD5 = new MD5CryptoServiceProvider();
  sChaineMD5 = oServiceMD5.ComputeHash(Encoding.ASCII.GetBytes(p_sChaine));

  // On convertie les bytes résultat en chaine de caractère

  foreach ( byte oByte in sChaineMD5 )
    oResult.Append(oByte.ToString("x2"));

  // On retourne le résultat

  return (oResult.ToString());
}

Changer les Forms par défaut VSTO - Outlook

by Nicolas Calvi 30. juin 2008 17:26
Il est possible avec VSTO de surcharger les formulaires par défauts d'Outlook, comme le formulaire d'ajout d'un contact par exemple. Pour cela il y a deux méthodes, l'un avec l'assistant (via Form Region) de Visual Studio, l'autre avec du code en interceptant l'ouverture de celle-ci. Dans le premier cas on surcharge ou on remplace définitivement un formulaire, mais si l'on veut par exemple ajouter un second répertoire contact dans Outlook et qu'en fonction du répertoire, afficher un formulaire différent, comme le formulaire par défaut pour le premier répertoire et un formulaire personnalisé sur le second, et bien avec la première méthode ce n'est pas possible.
Vient alors la seconde méthode qui consiste à surcharger le contrôleur d'instanciation (en fait une liste) des formulaires, nommé Inspectors sur l'objet d'application. Pour information un Inspector est un modèle de formulaire affichable dans Outlook pour la gestion d'un type d'objet précis (Appointement, Contact, ...) Quand, par exemple, on ajoute un nouveau contact, un nouvel Inspector est créé, dans le cas d'un contact c'est l'Inspector de type ContactItem qui est instancié. Si l'on veut donc substituer ce formulaire par notre propre WinForm, il faut d'abord s'abonner sur l'événement NewInspector du contrôleur pour qu'il nous informe de la création d'un nouvel Inspector.
public class ThisAddIn
{
  private Outlook.Inspectors m_oInspectors = null;

  private void ThisAddIn_Startup(object sender, System.EventArgs e)
  {
    this.m_oInspectors = this.Application.Inspectors;
    this.m_oInspectors.NewInspector += new Outlook.InspectorsEvents_NewInspectorEventHandler(this.Inspectors_NewInspector);
  }

  private void Inspectors_NewInspector(Outlook.Inspector p_oInspector)
  {
  }
}
Une fois abonné on peut donc intercepter les ouvertures et les empêcher en s'abonnant sur l'événement Open du nouvel Inspector. Dans cet événement on empêchera son ouverture avec la propriété Cancel de l'argument et ensuite on ouvrira notre propre WinForm.
private void Inspectors_NewInspector(Outlook.Inspector p_oInspector)
{
  // On vérifie que c'est le bon objet que l'on veut intercepter 
  if ( oItem is Outlook.ContactItem )
  {
    // On s'abonne pour pouvoir empêcher l'ouverture
    ((Outlook.ContactItem)oItem).Open += new Microsoft.Office.Interop.Outlook.ItemEvents_10_OpenEventHandler(InspectorItem_Open);

    // Affichage de notre propore WinForm
    using ( MaForm oForm = new MaForm )
    {
      oForm.ShowModal();
    }
  }
}

// Fonction qui va empêcher l'Inspector Contact de s'ouvrir
private void InspectorItem_Open(ref bool Cancel)
{
  Cancel = true;
}
Nous pouvons aussi effectuer cette opération seulement sur certaine actions, comme pour l'ajout d'un contact sur un répertoire que nous avons créé. Pour cela il suffit de vérifier si nous somme bien sur le bon répertoire et de traiter l'action si c'est le cas.
private void Inspectors_NewInspector(Outlook.Inspector p_oInspector)
{
  // On vérifie que c'est le bon objet que l'on veut intercepter 
  // Si c'est le bon on vérifie le nom du répertoire, si il est bon on substitue, sinon on laisse le formulaire par défaut
  if ( oItem is Outlook.ContactItem && this.Application.ActiveExplorer().CurrentFolder.Name == "Mon répertoire contact" )
  {
    // On s'abonne pour pouvoir empêcher l'ouverture
    ((Outlook.ContactItem)oItem).Open += new Microsoft.Office.Interop.Outlook.ItemEvents_10_OpenEventHandler(InspectorItem_Open);

    // Affichage de notre propore WinForm
    using ( MaForm oForm = new MaForm )
    {
      oForm.ShowModal();
    }
  }
}

// Fonction qui va empêcher l'Inspector Contact de s'ouvrir
private void InspectorItem_Open(ref bool Cancel)
{
  Cancel = true;
}
Voilà comment gérer de façon précise l'ouverture des formulaires sous Outlook.

Certifications

MVP

Microsoft Surface

MCTS

Microsoft .Net