Question

J'explore les notifications de requêtes avec le SQLDependency classe.Construire un exemple de travail simple est facile, mais j'ai l'impression qu'il me manque quelque chose.Une fois que j'ai dépassé un simple exemple d'une table/une dépendance, je me demande comment puis-je déterminer quelle dépendance a déclenché mon rappel ?

J'ai un peu de mal à expliquer, j'ai donc inclus l'exemple simple ci-dessous.Quand AChange() est appelé, je ne peux pas regarder le SQL à l'intérieur de la dépendance et je n'ai pas de référence à l'objet de cache associé.

Alors, que doit faire un garçon ?

  • Option 1 - créer une fonction distincte pour chaque objet que je souhaite suivre et coder en dur la clé de cache (ou les informations pertinentes) dans le rappel.Cela semble sale et élimine la possibilité d'ajouter de nouveaux éléments de cache sans déployer de nouveau code - ewww.
  • Option 2 - Utiliser la dépendance Id propriété et une structure de suivi parallèle

Est-ce que j'ai raté quelque chose ?Est-ce une lacune dans SQLDependency structure?J'ai consulté 20 articles différents sur le sujet et tous semblent avoir le même trou.Suggestions?

Exemple de code

public class DependencyCache{
   public static  string       cacheName  = "Client1";
   public static  MemoryCache  memCache   = new MemoryCache(cacheName);

   public DependencyCache() {
      SqlDependency.Start(connString);
   }

   private static string GetSQL() {
      return "select  someString FROM dbo.TestTable";
   }

   public void DoTest() {
      if (memCache["TEST_KEY"] != null ) {
         Debug.WriteLine("resources found in cache");
         return;
      }
      Cache_GetData();
   }

   private void Cache_GetData() {
      SqlConnection         oConn;
      SqlCommand            oCmd;
      SqlDependency         oDep;
      SqlDataReader         oRS;
      List<string>          stuff    = new List<string>();
      CacheItemPolicy       policy   = new CacheItemPolicy();

      SqlDependency.Start(connString);
      using (oConn = new SqlConnection(connString) ) {
         using (oCmd = new SqlCommand(GetSQL(), oConn) ) {
            oDep = new SqlDependency(oCmd);
            oConn.Open();
            oRS = oCmd.ExecuteReader();

            while(oRS.Read() ) {
                  resources.Add( oRS.GetString(0) );
            }

            oDep.OnChange += new OnChangeEventHandler (AChange);
         }
      }
      memCache.Set("TEST_KEY", stuff, policy);
   }

   private void AChange(  object sender, SqlNotificationEventArgs e) {
      string msg= "Dependency Change \nINFO: {0} : SOURCE {1} :TYPE: {2}";
      Debug.WriteLine(String.Format(msg, e.Info, e.Source, e.Type));

      // If multiple queries use this as a callback how can i figure 
      // out WHAT QUERY TRIGGERED the change?
      // I can't figure out how to tell multiple dependency objects apart

      ((SqlDependency)sender).OnChange -= Cache_SqlDependency_OnChange; 
      Cache_GetData(); //reload data
   }
}
Était-ce utile?

La solution

Tout d'abord:le gestionnaire doit être configuré avant la commande est exécutée :

 oDep = new SqlDependency(oCmd);
 oConn.Open();
 oDep.OnChange += new OnChangeEventHandler (AChange);
 oRS = oCmd.ExecuteReader();
 while(oRS.Read() ) {
     resources.Add( oRS.GetString(0) );
 }

Sinon, vous disposez d'une fenêtre pendant laquelle la notification peut être perdue et votre rappel n'a jamais été invoqué.

Maintenant à propos de votre question :vous devez utiliser un rappel distinct pour chaque requête.Bien que cela puisse sembler fastidieux, l'utilisation d'un lambda est en réalité triviale.Quelque chose comme ce qui suit :

oDep = new SqlDependency(oCmd);
oConn.Open();
oDep.OnChange += (sender, e) =>
{
   string msg = "Dependency Change \nINFO: {0} : SOURCE {1} :TYPE: {2}";
   Debug.WriteLine(String.Format(msg, e.Info, e.Source, e.Type));

   // The command that trigger the notification is captured in the context:
   //  is oCmd
   //
   // You can now call a handler passing in the relevant info:
   //
   Reload_Data(oCmd, ...);
};
oRS = oCmd.ExecuteReader();
...

Et n'oubliez pas de toujours vérifiez la source de notification, les informations et le type.Sinon, vous courez le risque de tourner à la nausée lorsque vous êtes averti pour des raisons autre que le changement de données, comme une requête invalide.En guise de commentaire secondaire, j'ajouterais qu'une bonne conception de cache n'actualise pas le cache en cas d'invalidation, mais invalide simplement l'élément mis en cache et laisse le prochaine demande en fait, récupérer un nouvel article.Avec votre approche « proactive », vous actualisez les éléments mis en cache même lorsqu'ils ne sont pas nécessaires, actualisez plusieurs fois avant d'y accéder, etc.J'ai omis de l'exemple la gestion des erreurs et la synchronisation appropriée des threads (toutes deux requises).

Enfin, jetez un oeil à LinqtoCache ce qui fait à peu près ce que vous essayez de faire, mais pour les requêtes LINQ.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top