Comment déterminer quelle fonction de changement a déclenché SQLDependency ?
-
11-12-2019 - |
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
}
}
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.