Notificaciones de consulta en ADO.NET 2.0
TOC
Collapse the table of content
Expand the table of content

Notificaciones de consulta en ADO.NET 2.0

Visual Studio 2005

Abril de 2005, Actualizado en junio de 2005

Publicado: 30 de Enero de 2006

Bob Beauchemin
DevelopMentor

Este artículo se aplica a:
ADO.NET 2.0

Resumen: conozca la forma de utilizar las nuevas tecnologías de notificación de ADO.NET 2.0 y SQL Server 2005 para controlar las actualizaciones de datos ad hoc. (14 páginas impresas.)

En esta página

Introducción Introducción
SqlDependency ofrece una solución para el almacenamiento en caché SqlDependency ofrece una solución para el almacenamiento en caché
Notificaciones de consulta en SQL Server 2005 Notificaciones de consulta en SQL Server 2005
Envío de notificaciones a un usuario final o una caché Envío de notificaciones a un usuario final o una caché
Uso de notificaciones de consulta desde un cliente de base de datos Uso de notificaciones de consulta desde un cliente de base de datos
Uso de SqlDependency Uso de SqlDependency
Uso de SqlNotificationRequest Uso de SqlNotificationRequest
Uso de SqlCacheDependency en ASP.NET Uso de SqlCacheDependency en ASP.NET
Notificaciones inteligentes Notificaciones inteligentes
Cuándo no se deben utilizar notificaciones: cuento con moraleja Cuándo no se deben utilizar notificaciones: cuento con moraleja
Conclusión Conclusión
Acerca del autor Acerca del autor

Introducción

Todas las aplicaciones de bases de datos relacionales que no son sencillas requieren una gran cantidad de tablas de búsqueda. Si su especialidad es crear código para interfaces gráficas de usuario, conocerá éstas como listas que rellenan los cuadros de lista desplegables. Clasifico las tablas de búsqueda en dos tipos: de sólo lectura y principalmente de lectura. La diferencia radica en qué puede provocar cambios en las tablas. Considero que una tabla es de sólo lectura si se requiere una reunión de personal o de usuarios para cambiarla. Un buen ejemplo sería una tabla que contenga categorías de los productos de una empresa. Esa tabla no cambiará a menos que la empresa lance un producto nuevo o realice una reorganización interna. Las tablas principalmente de lectura son listas relativamente constantes, pero que los usuarios finales pueden cambiar. Éstas se presentan habitualmente en cuadros combinados en lugar de en listas desplegables. Un ejemplo de este tipo sería una tabla de títulos de cortesía. Los diseñadores de aplicaciones pueden pensar en los títulos más habituales, como Sr., Sra., Srta. y Dr., pero siempre hay un usuario que tiene un título en el nunca se había pensado y que se desea agregar. Como ejemplo de lo frecuente que es este caso, el último producto de tamaño medio en el que trabajé contenía una base de datos relacional normal que contenía de 350 a 400 tablas. Calculo que aproximadamente 250 eran tablas de sólo lectura o principalmente de lectura.

En la aplicación Web tradicional (el ejemplo típico de una aplicación de tres niveles), desearía en todo lo que fuese posible almacenar en caché estos tipos de tablas. De este modo, no sólo se reduce el número de viajes de ida y vuelta a la base de datos, sino también la carga de consultas en la base de datos y, en consecuencia, ésta responde mejor en los casos de uso como los nuevos pedidos. Las tablas de sólo lectura son fáciles de almacenar en caché; se mantiene siempre la tabla en la caché y se proporciona al administrador de la base de datos (DBA) un método para volver a cargar la caché en el caso excepcional de que ésta tuviera que volver a cargar la tabla. Espero que en su empresa las reuniones que cambian la estructura básica y el contenido de las bases de datos se convoquen con muy poca frecuencia. La actualización de las tablas de búsqueda principalmente de lectura en una caché de nivel medio resulta un poco más problemática. Mediante la actualización de la caché programada con escasa frecuencia no se consigue el comportamiento deseado, ya que los usuarios no pueden ver los respectivos cambios de forma inmediata. Un encargado de soporte técnico podría agregar un nuevo elemento con una aplicación diferente y enviar un mensaje instantáneo a un amigo que intentara utilizarlo, pero la lista de opciones del amigo no contendría esa nueva entrada. Lo que es peor, si el segundo usuario intentara después volver a agregar la "entrada de lista que falta", recibiría un error de la base de datos en el que se indicaría que ese elemento ya existe. El almacenamiento en caché de las tablas principalmente de lectura no se suele realizar si éstas tienen más de un "punto de actualización" a causa de problemas de este tipo.

Anteriormente, los programadores recurrían a soluciones artesanales mediante las colas de mensajes, desencadenadores que escribían en archivos o protocolos fuera de banda para notificar a la caché cuando algún usuario actualizaba una tabla principalmente de lectura desde fuera de la aplicación. Estas soluciones de "señalización" simplemente notificaban a la caché que se había agregado o cambiado una fila, con el fin de indicar que se debía actualizar la caché. Notificar a la caché acerca de la fila específica que se ha cambiado o agregado es un problema diferente, que queda dentro de la competencia de las bases de datos distribuidas y las duplicaciones transaccionales o de combinación. En la solución de señalización con poca sobrecarga, una vez que el programa obtiene un mensaje de "caché no válida", éste simplemente actualiza la caché completa.

SqlDependency ofrece una solución para el almacenamiento en caché

Si utiliza SQL Server 2005 y ADO.NET 2.0 existe ahora una solución de señalización integrada en el proveedor de datos SqlClient y la base de datos de notificaciones de consulta. Por fin hay una solución integrada y fácil de usar para este frecuente problema. Las notificaciones de consulta también son directamente compatibles con una característica integrada en ASP.NET 2.0. La clase Cache de ASP.NET puede registrar las notificaciones y éstas se pueden utilizar incluso junto con el almacenamiento en caché de páginas y fragmentos de páginas que utiliza ASP.NET.

La infraestructura que desempeña esta útil función está compuesta por el motor de consulta de SQL Server 2005, SQL Server Service Broker, un procedimiento almacenado en el sistema, sp_DispatcherProc, las clases de ADO.NET SqlNotification, (System.Data.Sql.SqlNotificationRequest) y SqlDependency (System.Data.SqlClient.SqlDependency), y la clase Cache (System.Web.Caching.Cache). En resumen, el funcionamiento es el siguiente:

  1. Cada clase de ADO.NET SqlCommand contiene una propiedad Notification que representa una solicitud de notificación. Cuando se ejecuta la clase SqlCommand, la presencia de una propiedad Notification produce un paquete de protocolo de red (TDS) que indica una solicitud a la que se adjuntará una notificación.

  2. SQL Server registra una suscripción para la notificación solicitada con la infraestructura de notificaciones de consulta y ejecuta el comando.

  3. SQL Server "observa" en las instrucciones DML SQL cualquier cosa que pudiera producir un cambio en el conjunto de filas devuelto originalmente. Cuando se produce un cambio, se envía un mensaje a un servicio de Service Broker.

  4. Este mensaje puede:

    • a/ Hacer que una notificación se active de nuevo en el cliente que la registró.

    • b/ Permanecer en la cola del servicio de Service Broker disponible para el procesamiento personalizado por clientes avanzados.

      Figura 1. Descripción general de alto nivel de las notificaciones de consulta

La clase de ASP.NET SqlCacheDependency (System.Web.Caching.SqlCacheDependency) y la directiva OutputCache utilizan la capacidad de notificación automática mediante SqlDependency. Los clientes de ADO.NET que requieran un mayor control pueden utilizar SqlNotificationRequest y procesar la cola de Service Broker manualmente, implementando la semántica personalizada que deseen. Aunque en este artículo no se pretende ofrecer una explicación exhaustiva de Service Broker, el capítulo del libro de ejemplo A First Look at SQL Server 2005 for Developers y el artículo de Roger Wolter, A First Look at SQL Server 2005 Service Broker, le pueden ofrecer una excelente iniciación.

Antes de comenzar, es importante tener en cuenta que cada SqlNotificationRequest o SqlDependency obtiene un único mensaje de notificación cuando cambia el conjunto de filas. El mensaje es idéntico tanto si el cambio lo produce un comando INSERT de la base de datos, una instrucción DELETE que elimina una o varias filas o una instrucción UPDATE que actualiza una o varias filas. La notificación no contiene información sobre qué filas o cuántas han cambiado. Cuando el objeto de caché o la aplicación de usuario recibe el único mensaje de cambio, sólo tiene una opción: actualizar el conjunto de filas completo y volver a registrar la notificación. No se obtienen varios mensajes y una vez que se activa el mensaje único, desaparece la suscripción del usuario en la base de datos. El marco de las notificaciones de consulta también funciona con la premisa de que es preferible recibir notificaciones de más eventos que no recibir ninguna. Las notificaciones se envían no sólo cuando se cambia el conjunto de filas, sino también cuando una tabla que participa en el conjunto de filas se elimina o modifica, cuando la base de datos se recicla o por otros motivos. No obstante, la caché o la respuesta del programa es generalmente la misma: actualizar los datos almacenados en caché y volver a registrar la notificación.

Ahora que conoce la semántica general implicada, examinaremos detenidamente su funcionamiento desde tres perspectivas:

  1. Cómo se implementan las notificaciones de consulta en SQL Server y cómo funciona el distribuidor opcional.

  2. Cómo funcionan los atributos SqlDependency y SqlNotificationRequest de SqlClient en el cliente o nivel medio.

  3. Cómo admite ASP.NET 2.0 SqlDependency.

Notificaciones de consulta en SQL Server 2005

A nivel de servidor, SQL Server trata las consultas de los clientes en lotes. Cada consulta (considere la consulta como la propiedad SqlCommand.CommandText) puede contener sólo un lote, aunque éste puede incluir varias instrucciones T-SQL. SqlCommand también se puede utilizar para ejecutar un procedimiento almacenado o una función definida por el usuario, que puede contener varias instrucciones T-SQL. En SQL Server 2005, una consulta de un cliente también puede contener tres datos adicionales: el nombre de un servicio de Service Broker al que se entregan las notificaciones, un identificador de notificaciones (que es una cadena) y un tiempo de espera de notificación. Si estos tres datos están presentes en la solicitud de consulta y esta solicitud contiene las instrucciones SELECT o EXECUTE, SQL Server "buscará" en los conjuntos de filas producidos por la consulta cambios realizados por otras sesiones de SQL Server. Si se han producido varios conjuntos de filas, por ejemplo, en la ejecución de un procedimiento almacenado, SQL Server "buscará" en todos los conjuntos de filas.

¿Qué significa "buscar" en los conjuntos de filas y cómo realiza esta tarea SQL Server? La detección de cambios en un conjunto de filas forma parte del motor de SQL Server y emplea un mecanismo que existe desde SQL Server 2000: la detección de cambios para la sincronización de vistas indizadas. En SQL Server 2000, Microsoft introdujo el concepto de vistas indizadas. Una vista en SQL Server consiste en una consulta en las columnas de una o varias tablas. La vista tiene un nombre que se puede utilizar como nombre de tabla. Por ejemplo:

CREATE VIEW WestCoastAuthors
AS 
SELECT * FROM authors
  WHERE state IN ('CA', 'WA', 'OR')

Ahora se puede utilizar la vista como si fuera una tabla de una consulta como:

SELECT au_id, au_lname FROM WestCoastAuthors
  WHERE au_lname LIKE 'S%'

La mayoría de los programadores están familiarizados con las vistas, pero quizá no lo estén con las vistas indizadas. En una vista no indizada, los datos de ésta no se almacenan en una base de datos como una copia independiente; cada vez que se utiliza la vista, se ejecuta la consulta subyacente. Así, en el ejemplo anterior, se ejecutará la consulta para obtener el conjunto de filas WestCoastAuthors y ésta incluirá un predicado para extraer la vista WestCoastAuthors específica que se desee. Como una vista indizada almacena una copia de los datos, si convertimos WestCoastAuthors en una vista indizada, tendremos dos copias de los datos de estos autores. Ahora podremos actualizar los datos a través de dos vías diferentes: mediante la vista indizada o con la tabla original. Por lo tanto, SQL Server debe detectar los cambios en ambos almacenes de datos físicos para aplicar los cambios de uno en el otro. Este mecanismo de detección de cambios es el mismo que utiliza el motor cuando se usan las notificaciones de consulta.

Debido a la forma en que se implementa la detección de cambios, no todas las vistas se pueden indizar. Las limitaciones que se aplican a las vistas indizadas también se aplican a las consultas que se pueden utilizar para las notificaciones de consulta. Por ejemplo, la vista WestCoastAuthors no se puede indizar de la forma en que está escrita. Para indizarla, la definición de vista debe utilizar nombres con dos partes y nombrar todas las columnas del conjunto de filas de forma explícita. Por lo tanto, vamos a cambiar la vista para poder indizarla.

CREATE VIEW WestCoastAuthors
WITH SCHEMABINDING
AS
SELECT au_id, au_lname, au_fname, address, city, state, zip, phone
  FROM dbo.authors
  WHERE state in ('CA', 'WA', 'OR')

Sólo las consultas que cumplen las reglas de la vista indizada se pueden utilizar con las notificaciones. Tenga en cuenta que aunque se utiliza el mismo mecanismo para determinar si ha cambiado el resultado de una consulta, las notificaciones de consulta no hacen que SQL Server realice una copia de los datos, como ocurre con las vistas indizadas. Existe una lista de las reglas para vistas indizadas bastante amplia, que se puede encontrar en los Libros en pantalla de SQL Server 2005. Si se envía una consulta con una solicitud de notificación y ésta no cumple las reglas, SQL Server envía inmediatamente una notificación de consulta no válida. Pero, ¿dónde envía la notificación?

SQL Server 2005 utiliza la característica Service Broker para enviar notificaciones. Service Broker es una funcionalidad de cola asincrónica integrada en SQL Server. Las notificaciones de consulta utilizan los servicios de Service Broker. Un servicio en este caso es un destino para mensajes asincrónicos; se puede exigir que los mensajes cumplan un conjunto de reglas específico denominado contrato. Un servicio de Service Broker siempre está asociado a una cola, que es el destino físico de los mensajes. El contrato para las notificaciones de consulta está integrado en SQL Server con el nombre http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification.

Nota

Aunque el nombre del objeto de SQL Server sea una dirección URL, no implica nada sobre la ubicación. Es simplemente un nombre de objeto, del mismo modo que dbo.authors es el nombre de una tabla.

En resumen, el destino de los mensajes de las notificaciones de consulta puede ser cualquier servicio que admita el contrato adecuado. La DDL de SQL para definir un servicio de este tipo sería:

CREATE QUEUE mynotificationqueue
CREATE SERVICE myservice ON QUEUE mynotificationqueue
 ([http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification])
GO

Ahora se podría utilizar el servicio myservice como destino en una solicitud de notificación de consulta. SQL Server envía una notificación mediante el envío de un mensaje al servicio. Se puede utilizar un servicio propio o hacer que SQL Server use un servicio integrado en la base de datos MSDB. Si utiliza su propio servicio, deberá escribir código para leer los mensajes y procesarlos. Si utiliza el servicio integrado en MSDB, existe un código ya escrito que entrega el mensaje. Volveremos sobre este punto más adelante.

Como las notificaciones de consulta utilizan Service Broker, existen algunos requisitos adicionales.

  • Service Broker debe estar habilitado en la base de datos en la que se ejecuta la consulta de notificación. En Beta 2, no se encuentra habilitado de forma predeterminada en la base de datos de ejemplo AdventureWorks, pero se puede habilitar con una instrucción DDL "ALTER DATABASE SET ENABLE_BROKER".

  • El usuario que envía la consulta debe disponer de permiso para suscribirse a las notificaciones de consulta. Esto se realiza por base de datos; la siguiente DDL otorgaría permiso al usuario 'bob' para suscribirse a la base de datos actual:

    GRANT SUBSCRIBE QUERY NOTIFICATIONS TO bob
    

Envío de notificaciones a un usuario final o una caché

Hasta este punto, hemos enviado el tipo de lote de consultas correcto con una solicitud de notificación a SQL Server, que observa el conjunto de filas y, cuando alguien lo cambia, se envía un mensaje al servicio que hayamos elegido. ¿Y ahora qué ocurre? Se puede escribir código personalizado que se encargue de leer los mensajes y llevar a cabo la lógica que se desee cuando se produzcan las notificaciones. O se puede hacer que el distribuidor integrado se encargue de esto. Examinemos, entonces, el distribuidor.

A menos que se especifique un servicio personalizado, las notificaciones de consulta utilizan un servicio predeterminado de la base de datos MSDB denominado http://schemas.microsoft.com/SQL/Notifications/QueryNotificationService. Cuando los mensajes llegan a la cola de este servicio, son automáticamente procesados por un procedimiento almacenado asociado a la cola y denominado sp_DispatcherProc. Un aspecto interesante es que este procedimiento utiliza código escrito en .NET, por lo que la carga de Common Language Runtime (CLR) de .NET deberá estar habilitada en la instancia de SQL Server 2005 para que funcione la entrega de notificación de consulta automática. (La carga de CLR de .NET se puede habilitar o deshabilitar para cada instancia de SQL Server.)

Cuando llega un mensaje de notificación de consulta, sp_DispatcherProc (al que en adelante llamaré "el distribuidor") examina la lista de la cola de notificaciones de las suscripciones de notificaciones de consulta SqlDependency y envía un mensaje a cada suscriptor. Tenga en cuenta que cuando se utiliza el distribuidor, el servidor notifica al cliente que han cambiado los datos. Esto resulta útil por dos motivos: el cliente no tiene que sondear en busca de notificaciones ni necesita mantener una conexión a SQL abierta para recibir las notificaciones. El distribuidor envía esta notificación a cada suscriptor mediante el protocolo HTTP o TCP y un protocolo privado. La comunicación del servidor con el cliente se puede autenticar de forma opcional. Una vez entregada la notificación, la suscripción se elimina de la lista de suscripciones activas. Recuerde que cada suscripción de cliente sólo obtiene una notificación; el cliente decidirá si reenviar la consulta y volver a suscribirse.

Uso de notificaciones de consulta desde un cliente de base de datos

Ahora que conocemos los mecanismos internos, crearemos un cliente de ADO.NET que los utilice. ¿A qué se deben tantas explicaciones antes de escribir código de cliente relativamente sencillo? Aunque la escritura del código resulte bastante sencilla, no debemos olvidar cumplir las reglas. Los problemas más habituales son enviar una consulta para notificaciones no válida y olvidarse de configurar Service Broker y los permisos de usuario. Esto ha causado frustración en relación a esta eficaz característica, e incluso algunos comprobadores de la versión beta han creído que no funcionaba. Un poco de trabajo de preparación e investigación puede resultar de enorme ayuda. Por último, es útil comenzar por el mecanismo interno puesto que vamos a especificar propiedades como los servicios de Service Broker y protocolos para el distribuidor, y ahora ya sabe a qué se refieren estos términos.

Se puede crear un cliente de notificaciones de consulta en ADO.NET tal como lo haremos, mediante OLE DB o incluso con el nuevo cliente de servicios Web HTTP, pero un aspecto que no se debe olvidar es que las notificaciones de consulta sólo están disponibles a través de código de cliente. No se puede utilizar esta característica directamente con T-SQL o con código de procedimiento SQLCLR que emplea el proveedor de datos de SqlServer para comunicar con SQL Server.

El ensamblado System.Data.dll contiene dos clases que se pueden utilizar: SqlDependency y SqlNotificationRequest. SqlDependency se utiliza cuando se desea la notificación automática mediante el distribuidor. Con SqlNotificationRequest es uno mismo el que procesa los mensajes de notificación. Examinaremos un ejemplo de cada una de ellas.

Uso de SqlDependency

Los pasos para utilizar SqlDependency son sencillos. Primero, cree un comando SqlCommand que contenga las instrucciones SQL para las que desea las notificaciones de consulta. Asocie SqlCommand con una clase SqlDependency. A continuación, registre un controlador de eventos para el evento OnChanged de SqlDependency. Después, ejecute SqlCommand. Puede procesar DataReader, cerrarlo e incluso cerrar el SqlConnection asociado; el distribuidor le notificará cuando se produzca un cambio en el conjunto de filas. Los eventos para este objeto se activan en otros subprocesos; esto significa que tendrá que hacer frente a situaciones en las que el evento se activará mientras aún se esté ejecutando el código. Incluso puede que aún esté procesando los resultados del lote cuando se active el evento. Aquí tenemos el código:

using System;
using System.Data;
using System.Data.SqlClient;
static void Main(string[] args)
{
  string connstring = GetConnectionStringFromConfig();
  using (SqlConnection conn = new SqlConnection(connstring))
  using (SqlCommand cmd = 
   // 2-part table names, no "SELECT * FROM ..."
   new SqlCommand("SELECT au_id, au_lname FROM dbo.authors", conn))
 {
  try 
  {
    // create dependency associated with cmd
    SqlDependency depend = new SqlDependency(cmd);
    // register handler
    depend.OnChanged += new OnChangedEventHandler(MyOnChanged);
    conn.Open();
    SqlDataReader rdr = cmd.ExecuteReader();
    // process DataReader
    while (rdr.Read())
    Console.WriteLine(rdr[0]);
    rdr.Close();
    // Wait for invalidation to come through
    Console.WriteLine("Press Enter to continue");
    Console.ReadLine();
  }
  catch (Exception e)
   { Console.WriteLine(e.Message); }
 }
}
static void MyOnChanged(object caller, SqlNotificationEventArgs e)
{
  Console.WriteLine("result has changed");
  Console.WriteLine("Source " + e.Source);
  Console.WriteLine("Type " + e.Type);
  Console.WriteLine("Info " + e.Info);
}

Puede escribir el mismo código en Visual Basic .NET mediante la conocida palabra clave WithEvents junto con SqlDependency. Tenga en cuenta que este programa sólo obtendrá y procesará un único evento OnChanged, independientemente de las veces que cambien los resultados subyacentes. Para cualquier uso que no sea trivial, lo que realmente deseamos hacer cuando recibimos una notificación es reenviar el comando con una notificación actualizada y utilizar sus resultados para actualizar la caché con los datos nuevos. Si tomamos el código de Main() del ejemplo anterior y lo movemos a una rutina con nombre, nuestro código podría presentar este aspecto:

static void Main(string[] args)
{
    GetAndProcessData(); 
    UpdateCache();
    // wait for user to end program
    Console.WriteLine("Press Enter to continue");
    Console.ReadLine();
}
static void MyOnChanged(object caller, SqlNotificationEventArgs e)
{
    GetAndProcessData(); 
    UpdateCache(); 
}

Veremos en algunos párrafos que esto es exactamente lo que se puede hacer en ASP.NET 2.0, utilizar la clase Cache de ASP.NET como caché de datos.

Cuando se emplea SqlDependency se cuenta con que el distribuidor integrado en SQL Server 2005 establecerá una conexión con el cliente para enviar el mensaje de notificación. Esta comunicación se realiza fuera de banda y, por lo tanto, no utiliza SqlConnection. Esto también implica que SQL Server debe poder obtener acceso al cliente a través de la red; los servidores de seguridad y la traducción de direcciones de red podrían interferir. Es posible que las próximas versiones beta permitan un mayor control de la configuración del puerto para establecer más fácilmente el servidor de seguridad. Se pueden especificar parámetros en el constructor SqlDependency para configurar completamente la forma en que funciona la comunicación del servidor con el cliente. Aquí se muestra un ejemplo:

SqlDependency depend = new SqlDependency(cmd,
    null,
    SqlNotificationAuthType.None,
    SqlNotificationEncryptionType.None,
    SqlNotificationTransports.Tcp,
    10000);

Con este constructor de SqlDependency se pueden elegir comportamientos diferentes a los establecidos de forma predeterminada. El comportamiento más útil para cambiar es el transporte por cable que el servidor emplea para conectar con el cliente. Este ejemplo utiliza SqlNotificationTransports.Tcp; el servidor puede usar TCP o HTTP. El valor predeterminado de este parámetro es SqlNotificationTransports.Any; esto permite al servidor "decidir" qué transporte debe utilizar. Si se especifica Any, el servidor elegirá HTTP cuando el sistema operativo del cliente contenga compatibilidad con HTTP en modo de núcleo o TCP en caso contrario. Windows Server 2003 y Windows XP con SP2 contienen esta clase de compatibilidad. Como los mensajes se envían a través de Internet, puede especificar qué tipo de autenticación se utilizará. EncryptionType es actualmente un parámetro, aunque se quitará en versiones beta posteriores. Actualmente, ambos valores se establecen en None de forma predeterminada. SqlNotificationAuthType también admite la autenticación integrada. Asimismo, se puede especificar explícitamente el valor del tiempo de espera para la suscripción y el nombre del servicio de Service Broker de SQL Server. El nombre del servicio se establece habitualmente en null, como en el ejemplo, pero se puede especificar de forma explícita el servicio integrado, SqlQueryNotificationService. Los parámetros que más probablemente sobrescriba son SqlNotificationTransport y el tiempo de espera. Tenga en cuenta que estos parámetros sólo se aplican a SqlDependency puesto que especifican los comportamientos del distribuidor del servidor. Con SqlNotificationRequest, no se utiliza el distribuidor.

Uso de SqlNotificationRequest

El uso de SqlNotificationRequest es sólo un poco más complicado que el de SqlDependency en cuanto a la configuración pero, si lo desea, el programa puede procesar los mensajes. Cuando se utiliza SqlDependency, las notificaciones en el servidor se enviarán a SqlQueryNotificationService en MSDB, que se encargará de procesar los mensajes. Con SqlNotificationRequest, debe procesar los mensajes personalmente. A continuación se muestra un ejemplo sencillo en el que se utiliza SqlNotificationRequest y el servicio que definimos anteriormente en este artículo.

using System;
using System.Data;
using System.Data.Sql;
using System.Data.SqlClient;
class Class1
{
  string connstring = null;
  SqlConnection conn = null;
  SqlDataReader rdr = null;
  static void Main(string[] args)
  {
    connstring = GetConnectionStringFromConfig();
    conn = new SqlConnection(connstring));
    Class1 c = new Class1();
    c.DoWork();  
  }
  void DoWork()
  {
    conn.Open();
    rdr = GetJobs(2);
    if (rdr != null)
    {
      rdr.Close();
      WaitForChanges();
    }
    conn.Dispose();
  }
  public SqlDataReader GetJobs(int JobId)
  {
    using (SqlCommand cmd = new SqlCommand(
        "Select job_id, job_desc from dbo. jobs where job_id = @id", 
        conn))
    {
      
    try
    {
      cmd.Parameters.AddWithValue("@id", JobId);
      SqlNotificationRequest not = new SqlNotificationRequest();
      not.Id = new Guid();
      // this must be a service named MyService in the pubs database
      // associated with a queue called notificationqueue (see below)
      // service must go by QueryNotifications contract
      not.Service = "myservice";
      not.Timeout = 0; 
      // hook up the notification request
      cmd.Notification = not;
      rdr = cmd.ExecuteReader();
      while (rdr.Read())
     Console.WriteLine(rdr[0]);
     rdr.Close();
    }
    catch (Exception ex)
    { Console.WriteLine(ex.Message); }
    return rdr;
    }
  }
  public void WaitForChanges()
  {
    // wait for notification to appear on the queue
    // then read it yourself
    using (SqlCommand cmd = new SqlCommand(
     "WAITFOR (Receive convert(xml,message_body) from notificationqueue)",    
      conn))
    {
      object o = cmd.ExecuteScalar();
      // process the notification message however you like
      Console.WriteLine(o); 
    }
  }

La ventaja (así como el trabajo adicional) de utilizar SqlNotificationRequest radica en que es necesario esperar la notificación y procesarla uno mismo. Cuando se utiliza SqlDependency, no es necesario volver a conectar a la base de datos hasta que se recibe la notificación. No se tiene que esperar en realidad a la notificación SqlNotificationRequest; se puede sondear la cola cada cierto tiempo. Otro uso de SqlNotificationRequest podría ser el de crear una aplicación especializada que ni siquiera esté en ejecución al activarse la notificación. Cuando se inicia la aplicación, ésta puede conectar con la cola y determinar qué resultados en su "caché persistente" (desde la ejecución de una aplicación anterior) no son válidos ahora.

Al abordar las aplicaciones que pueden esperar durante horas o días una notificación, se plantea una pregunta: "si no se producen cambios en los datos, ¿cuándo desaparece la notificación?". Lo único que puede hacer que una notificación desaparezca (es decir, que se purgue de las tablas de suscripción de la base de datos) es que ésta se active o caduque. Para los administradores de bases de datos a los que les resulte incómodo que haya suscripciones en espera (porque utilizan recursos de SQL y agregan sobrecarga a las consultas y actualizaciones) existe una forma de disponer manualmente de una notificación de SQL Server. Primero, se consultan las vistas dinámicas de SQL Server 2005 y se encuentra la suscripción de notificación errónea. A continuación, se ejecuta el comando para eliminarla.

  -- look at all subscriptions
  SELECT * FROM sys.dm_qn_subscriptions
  -- pick the ID of the subscription that you want, then
  -- say its ID = 42
  KILL QUERY NOTIFICATION SUBSCRIPTION 42

Uso de SqlCacheDependency en ASP.NET

Las notificaciones también están enlazadas a la clase Cache de ASP.NET. En ASP.NET 2.0 la clase CacheDependency se puede incluir en subclases y SqlCacheDependency encapsula SqlDependency y se comporta igual que cualquier otra clase CacheDependency de ASP.NET. La ventaja de SqlCacheDependency respecto a SqlDependency es que funciona con SQL Server 2005 y con versiones anteriores de SQL Server, aunque en estas últimas se implementa de una forma totalmente distinta.

Cuando se utilizan versiones anteriores de SQL Server, SqlCacheDependency funciona mediante desencadenadores en las tablas que se desee "observar". Estos desencadenadores escriben filas en una tabla de SQL Server diferente. A continuación, se sondea esta tabla. Se puede configurar qué tablas están habilitadas para las dependencias y el valor del intervalo de sondeo. Los detalles relativos a la implementación previa a SQL Server 2005 quedan fuera del ámbito de este artículo; si desea obtener más información, consulte el artículo "Improved Caching in ASP.NET 2.0".

Al utilizar SQL Server 2005, SqlCacheDependency encapsula una instancia SqlDependency similar al ejemplo de ADO.NET mostrado anteriormente. A continuación se ofrece un ejemplo de código en el que se muestra el uso de SqlCacheDependency.

// called from Page.Load
CreateSqlCacheDependency(SqlCommand cmd)
{
  SqlCacheDependency dep = new SqlCacheDepedency(cmd);
  Response.Cache.SetExpires(DateTime.Now.AddSeconds(60);
  Response.Cache.SetCacheability(HttpCacheability.Public);
  Response.Cache.SetValidUntilExpires(true);
  Response.AddCacheDependency(dep);
}

Una característica interesante y fácil de usar es que SqlCacheDependency se enlaza incluso en el almacenamiento en caché de páginas o fragmentos de página. Se pueden habilitar mediante declaración todos los comandos SqlCommands en una directiva OutputCache de ASP.NET específica. Ésta utiliza la misma instancia SqlDependency para todos los SqlCommands de la página y presenta este aspecto en una base de datos de SQL Server 2005.

<%OutputCache SqlDependency="CommandNotification" ... %>

Tenga en cuenta que CommandNotification es un valor de palabra clave que significa "utilizar SQL Server 2005 y SqlDependency"; la sintaxis para el parámetro de esta directiva es completamente diferente cuando se utilizan versiones anteriores de SQL Server. Asimismo, el valor clave CommandNotification sólo se habilita cuando se ejecuta ASP.NET 2.0 en versiones específicas de sistemas operativos.

Notificaciones inteligentes

Según una directiva de diseño de las notificaciones de consulta de SQL Server, es preferible que se notifique a un cliente con demasiada frecuencia que perder una notificación. Aunque el caso más probable es que se le notifique cuando alguien ha cambiado una fila que invalida su caché, no siempre es así. Por ejemplo, si el administrador de la base de datos recicla la base de datos, recibirá una notificación. Si alguna de las tablas de su consulta se modifica, elimina o trunca, se le notificará. Como las notificaciones de consulta utilizan recursos de SQL Server, es posible que si SQL Server tiene una carga de recursos excesiva, comience a quitar notificaciones de consulta de las tablas internas; en este caso también recibirá una notificación en el cliente. Y como cada solicitud de notificación incluye un valor de tiempo de espera, se le notificará cuando finalice el tiempo de espera de su suscripción.

Si utiliza SqlDependency, el distribuidor incluirá esta información en una instancia SqlNotificationEventArgs. Esta clase contiene tres propiedades: Info, Source y Type que le permitirán identificar el motivo de la notificación. Si utiliza SqlNotificationRequest, el campo message_body en el mensaje en cola contiene un documento XML que incluye la misma información, pero tendrá que analizarla uno mismo con XPath o XQuery. Aquí se muestra un ejemplo del documento XML que se obtuvo del anterior ejemplo de SqlNotificationRequest de ADO.NET.

<qn:QueryNotification 
 xmlns:qn="http://schemas.microsoft.com/SQL/Notifications/QueryNotification" 
 id="2" type="change" source="data" info="update" 
 database_id="6" user_id="1">
<qn:Message>{CFD53DDB-A633-4490-95A8-8E837D771707}</qn:Message>
</qn:QueryNotification>

Tenga en cuenta que aunque he producido esta notificación al cambiar el valor de la columna job_desc por "new job" en la columna job_id = 5, nunca verá esta información en el propio cuerpo del mensaje message_body. Esto plantea unos últimos matices respecto al proceso de notificación. La notificación sólo puede reconocer que una instrucción SQL ha modificado algo que podría cambiar el conjunto de filas. No puede saber que la instrucción UPDATE no cambia el valor real de una fila. Por ejemplo, el cambio de una fila de job_desc = "new job" a job_desc = "new job" produciría una notificación. Asimismo, como las notificaciones de consulta son asincrónicas y se registran en el momento en que se ejecuta el comando o el lote, es posible recibir una notificación antes de terminar la lectura del conjunto de filas. También puede recibir una notificación inmediata si envía una consulta que no se ajusta a las reglas; se trata de las reglas para vistas indizadas mencionadas anteriormente.

Cuándo no se deben utilizar notificaciones: cuento con moraleja

Ahora que ya sabe cómo funcionan las notificaciones, le resultará fácil reconocer dónde utilizarlas: en las tablas de búsqueda principalmente de lectura. Cada conjunto de filas de una notificación utiliza recursos de SQL Server; emplearlos para tablas de sólo lectura sería desaprovecharlos. Además, no deseamos utilizarlos para consultas ad hoc, ya que habría demasiados conjuntos de filas diferentes "observados" al mismo tiempo. Un detalle interno que conviene conocer es que SQL Server agrupa los recursos de notificación para consultas parametrizadas que emplean diferentes conjuntos de parámetros. El uso de consultas parametrizadas (como se muestra en el ejemplo de SqlNotificationRequest anterior) le permitirá aprovechar esta característica y conseguir un rendimiento mejorado. Si esto le preocupa, tenga en cuenta que esta característica de rendimiento no implica que no vaya a obtener las notificaciones adecuadas. Si el usuario 1 (user1) observa los autores con au_lname desde la A a la M y el usuario 2 (user2) observa au_lname desde la N a la Z con los valores au_lname como parámetros, cada usuario sólo obtendrá las notificaciones "correctas" para su subconjunto.

Una última advertencia: algunas personas, cuando piensan en aplicaciones de notificación, se imaginan una sala llena de agentes de bolsa con precios de mercado que varían y donde las pantallas cambian continuamente. Este uso de la característica es totalmente inadecuado por dos motivos.

  • Los conjuntos de filas cambian continuamente, por lo que la red puede saturarse a causa de las notificaciones de consulta y las solicitudes de actualización de consultas.

  • Si hay un número importante de usuarios y todos "observan" los mismos datos, cada notificación producirá que muchos usuarios vuelvan a consultar los mismos resultados al mismo tiempo. De esta forma se podría saturar SQL Server con numerosas solicitudes para los mismos datos.

Si cree que sus programadores podrían abusar de esta característica, le alegrará saber que en la versión posterior a la beta 2, SQL Server puede proporcionar más información de modo que permita a los administradores de bases de datos controlar esta característica mediante vistas de administración dinámicas. Actualmente, estas vistas sólo pueden mostrar las suscripciones. Recuerde que SQL Server 2005 siempre puede "decidir" que las notificaciones utilizan demasiados recursos y comenzar a purgarlas.

Conclusión

Las notificaciones de consulta constituyen una característica nueva de gran eficacia, integrada en SQL Server 2005, que se puede utilizar desde ADO.NET 2.0 y desde ASP.NET 2.0 directamente. Aunque la característica de ADO.NET (SqlNotificationRequest y SqlDependency) SÓLO funciona con una base de datos de SQL Server 2005, ASP.NET permite que "esté disponible en versiones anteriores" mediante un mecanismo alternativo que utiliza el sondeo. Emplee esta característica con precaución, teniendo en cuenta la semántica y las repercusiones. El uso más idóneo de esta característica es con la tabla de búsquedas principalmente de lectura utilizada por ASP.NET que otras aplicaciones podrían actualizar, además de la aplicación Web. En este escenario, las notificaciones de consulta ofrecen una solución que los programadores han deseado desde hace años.

Acerca del autor

Bob Beauchemin trabaja como profesor, autor de cursos y coordinador de programaciones de cursos sobre bases de datos en DevelopMentor. Cuenta con más de veinticinco años de experiencia como arquitecto, programador y administrador de sistemas distribuidos centrados en datos. Ha escrito artículos sobre ADO.NET, OLE DB y SQL Server para Microsoft Systems Journal, SQL Server Magazine y otras publicaciones, además de ser el autor de A First Look at SQL Server 2005 for Developers y Essential ADO.NET (ambos en inglés).

Mostrar:
© 2016 Microsoft