Pergunta

Estou explorando notificações de consulta com o SQLDependency aula.Construir um exemplo simples de trabalho é fácil, mas sinto que está faltando alguma coisa.Depois de passar por um exemplo simples de uma tabela/uma dependência, fico pensando como posso descobrir qual dependência acionou meu retorno de chamada?

Estou tendo dificuldades para explicar, então incluí o exemplo simples abaixo.Quando AChange() é chamado, não consigo ver o sql dentro da dependência e não tenho uma referência ao objeto de cache associado.

Então, o que um menino deve fazer?

  • Opção 1 - criar uma função distinta para cada objeto que desejo rastrear e codificar a chave de cache (ou informações relevantes) no retorno de chamada.Isso parece sujo e elimina a possibilidade de adicionar novos itens de cache sem implantar um novo código - ewww.
  • opção 2 - Use a Dependência Id propriedade e uma estrutura de rastreamento paralela

Estou apenas faltando alguma coisa?Isso é uma deficiência no SQLDependency estrutura?Já li 20 artigos diferentes sobre o assunto e todos parecem ter a mesma lacuna.Sugestões?

Amostra de código

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
   }
}
Foi útil?

Solução

Em primeiro lugar:o manipulador deve ser configurado antes o comando é executado:

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

Caso contrário, você terá uma janela quando a notificação poderá ser perdida e seu retorno de chamada nunca será invocado.

Agora sobre sua pergunta:você deve usar um retorno de chamada separado para cada consulta.Embora isso possa parecer complicado, na verdade é trivial usar um lambda.Algo como o seguinte:

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();
...

E lembre-se de sempre verifique a fonte, informações e tipo da notificação.Caso contrário, você corre o risco de ficar enjoado quando for notificado por motivos outro do que alteração de dados, como consulta inválida.Como comentário adicional, eu acrescentaria que um bom design de cache não atualiza o cache na invalidação, mas simplesmente invalida o item armazenado em cache e permite que o próxima solicitação realmente buscar um item novo.Com sua abordagem 'proativa', você atualiza itens em cache mesmo quando não são necessários, atualiza várias vezes antes de serem acessados, etc.Deixei de fora o exemplo de tratamento de erros e sincronização adequada de threads (ambos necessários).

Por fim, dê uma olhada LinqtoCache que faz praticamente o que você está tentando fazer, mas para consultas LINQ.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top