Como descobrir qual SQLDependency acionou a função de mudança?
-
11-12-2019 - |
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
}
}
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.