Beren.it

All that you can leave behind

SQL Cache Dependency con SQL Server, LINQ e ASP.NET 3.5

Riporto qui sotto una libera traduzione di un post molto interessante sul quale mi sono imbattuto qualche giorno fa sul tema del caching in sistemi multiserver. 

ASP.NET fornisce diverse opzioni di caching. In questo caso prenderemo in esame una feature poco conosciuta di ASP.NET 3.5 chiamata SQL Cache Dependency. SQL Cache Dependency nasce in realtà con SQL Server 2000, ma la sua utilità era limitata per via del meccanismo di polling che imponeva un continuo check del DB per individuarne le modifiche, e a modifica individuata, notificava alla web application che la cache andava aggiornata.

Con SQL Server 2005 e 2008, ASP.NET ha adottato finalmente un sistema più performante per la SQL Cache Dependency. Lo sviluppatore può ora infatti delegare direttamente a SQL Server il compito di informarlo quando una modifica ha avuto luogo. In pratica ora la web application non deve continuamente monitorare il DB per accorgersi di eventuali modifiche.

Nell'esempio seguente useremo SQL Server 2005 e il database Pubs che potete scaricare qui. Anzitutto per consentire a SQL Server di inviare le notifiche occorre abilitare il Microsoft SQL Server 2005 Service Broker. Per verificare se questo servizio è già abilitato sul Catalog (Pubs n.d.r.) specificato basta lanciare il seguente script SQL:

[T-SQL]


SELECT name, is_broker_enabled FROM sys.databases

Se la riga con il nome del catalog interessato ha un 1 nella colonna is_broken_enabled, significa che il servizio è già attivo altrimenti bisogna attivarlo. Per farlo occorre lanciare il seguente script che sfrutta il comando ALTER DATABASE:

[T-SQL]


ALTER DATABASE Pubs SET ENABLE_BROKER
GO

Qualora però il database sia in uso il comando precedente sembra non terminare mai, e googlando ho scoperto qui il modo per farlo terminare istantaneamente.

[T-SQL]


ALTER DATABASE Pubs SET ENABLE_BROKER WITH ROLLBACK IMMEDIATE
GO

A questo punto occorre informare SQL Server che l'account con cui gira Internet Information Services (IIS) ha i permessi per sottoscriversi alle notifiche. Lo script seguente spiega esattamente come effettuare questa operazione con l'account ASPNET sul server TESTSERVER:

[T-SQL]


GRANT SUBSCRIBE QUERY NOTIFICATIONS TO 'TESTSERVER\ASPNET'

Ora che il Database è stato correttamente configurato, procediamo con il creare il Website ASP.NET che utilizzi la SQL Cache Dependecy. Apriamo Visual Studio 2008 e clicchiamo su File > New > Web > ASP.NET Web Application.

t;p>Di default, Visual Studio 2008 crea una pagina Default.aspx alla creazione di una nuova Web Application. Useremo questa pagina per interrogare il Database, SQL Cache Dependency per "cachare" i dati ritornatici ed infine visualizzarli.

 

Apriamo la pagina e trasciniamoci dentro un DropDownBox control, infine rinominiamolo cboDiscount.

[ASPX/HTML]

<form id="form1" runat="server">
   <div>
      <asp:DropDownList id="cboDiscount" runat="server" >
      </asp:DropDownList>
   </div>
</form>

In questo caso useremo LINQ to SQL come access data layer. Aggiungiamo quindi un nuovo file di tipo LINQ to SQL Classes al progetto:

Apriamo il file appena creato e trasciniamo attraverso il designer la tabella Discounts al suo interno.

Trascinando la tabella il designer creerà una nuova chiave di tipo Connection String nel web.config simile a questa:

[XML]

<connectionStrings>
   <add name="pubsConnectionString" 
            connectionString="Data Source=dotnet-testbed;Initial Catalog=Pubs;Integrated Security=True"  
            providerName="System.Data.SqlClient" />
</connectionStrings>

Per abilitare la ricezione delle notifiche occorre creare un listener. Il punto migliore per collocarlo è nel file Global.asax che solitamente si trova nel root folder delle applicazioni ASP. Aggiungiamone uno al progetto e quindi andiamo ad inserire nel codice l'attivazione e la disattivazione del listener:

[C#]

protected void Application_Start(object sender,EventArgs  e)
{
   SQLDependency.Start(ConfigurationManager.ConnectionString["pubsConnectionString"].ConnectionString); 
}
protected void Application_End(object sender,EventArgs  e)
{
   SQLDependency.Stop(ConfigurationManager.ConnectionString["pubsConnectionString"].ConnectionString); 
}

Il prossimo passo è scrivere la parte di codice che setti la cache dependency. In questo caso il codice è inserito in un Extension Method introdotti da C# 3.0 and VB.NET 9.0, che consente allo sviluppatore di estendere una classe esistente creando dei metodi che possono essere aggiunti alla classe esistente. Per farlo aggiungiamo una nuovo file di tipo Class e lo nominiamo MyExtensions. Gli Extension Method sono statici e perciò vanno marcati con la keyword static.

[C#]

public static class MyExtensions
{
   public static List<T> LinqCache<T> (this Table<T> query) where T : class
   {
      string tableName = query.Context.Mapping.GetTable(typeof(T)).TableName;
      List<T> result = HttpContext.Current.Cache[tableName] as List<T>;

      if (result == null)
      {
         using(SqlConnection  cn = new SqlConnection(query.Context.Connection.ConnectionString))
         {
            cn.Open();
            SqlCommand  cmd = new SqlCommand(query.Context.GetCommand(query).CommandText, cn);
            cmd.Notification = null;
            cmd.NotificationAutoEnlist = null;
            SqlCacheDependencyAdmin.EnableNotifications(query.Context.Connection.ConnectionString);
            if (!SqlCacheDependencyAdmin.GetTablesEnabledForNotifications(query.Context.Connection.ConnectionString).Contains(tableName))
            {
               SqlCacheDependencyAdmin.EnableTableForNotifications(query.Context.Connection.ConnectionString, tableName);
            }
            SqlCacheDependency dependency = new SqlCacheDependency(cmd);
            cmd.ExecuteNonQuery();

            result = query.ToList();
            HttpContext.Current.Cache.Insert(tableName, result, dependency);
        }
     }
     return result;
   }
}

Questo è a tutti gli effetti un Extension Method per la classe System.Data.Linq.Table<> e può essere utilizza con qualsiasi LINQ query. Il cuore di questo metodo è SqlCacheDependencyAdmin.EnableNotifications che effettua il check per capire se il Service Broker è abilitato. Se così è verifica che la tabella sia già abilitata alla notifica tramite SqlCacheDependencyAdmin.GetTablesEnabledForNotifications. Se così non fosse SqlCacheDependencyAdmin.EnableTableForNotifications abilita le notifiche sulla tabella.

La prima volta che questo Extension Method verrà invocato verrà creata la tabella AspNet_SqlCacheTablesForChangeNotification nel database Pubs.

Aprendola si noterà che è stata aggiunta una riga a testimonianza che il Service Broker è funzionante.

A questo punto, come riprova, cambiando un valore nella tabella vi accorgerete che la cache ASP.NET verrà correttamente pulita.

Sebbene questo sistema non migliori sensibilmente le performance di una web application, certamente può essere di aiuto nel caso di sistemi distribuiti su più server. In particolare con tutte quelle tabelle che non varino in continuazione. Qui potete trovare il codice della solution, sempre riconducibile al post citato in partenza.

Mondiale 2010... capolinea!

Il nostro mondiale africano è finito, anzi come dice qualcuno in realtà non è mai cominciato. La nazionale di Lippi, non ha mai convinto ed anzi ha stupito e per la pochezza degli interpreti e per il gioco di squadra. Terminiamo la fase eliminatoria come ultimi del girone, non era mai successo, e senza riuscire a vincere una sola gara (come nell'82). Peggio, aggiungo, non riuscendo mai ad essere neppure temporaneamente sopra ai nostri avversari. Sempre perennemente in rimonta. Sempre con l'acqua alla gola, sempre con la sensazione che non ce la si faccia. Dopotutto la cabala non era dalla nostra parte: non abbiamo mai vinto un mondiale fuori dall'europa e siamo andati in finale solo due volte nelle americhe in Messico e USA, perdendo entrambe contro il Brasile. In genere poi dopo il mondiale vinto o perso in finale nell'edizione successiva abbiamo poi fatto figure barbine: nel 74 in Germania fuori ai gironi, in Messico nell'86 fuori agli ottavi, in Francia nel 98 fuori ai quarti...
Così ora oltre all'amarezza ed alla delusione per un risultato che certo non può fare piacere, come ogni anno ci tocca assistere alla gogna rituale su cui viene giustiziata, squadra, allenatore, dirigenza e quant'altro... Parliamoci chiaro, una debacle, di questo genere non può e non deve avere scusanti di nessun tipo e fortunatamente Lippi lo ha ammesso senza appelli «Mi prendo tutte le responsabilità per le scelte. Non ho preparato la squadra in maniera giusta». WOW è già una notizia, in un paese abituato a sentirsi dire che quando le cose vanno male le spiegazioni stanno sempre altrove ed adopera di terzi. Ma questo è un altro discorso.
Il fatto è che ancora una volta si cercano colpevoli un pò in tutto, pensate che ieri mattina ad una trasmissione sulla Rai ho sentito tirare in mezzo gli stadi vecchi, calciopoli... della serie "l'è tutto sbagliato l'è tutto da rifare" di Bartaliana memoria. Io sinceramente, penso che ci possano essere varie ragioni su questa che è senza dubbio una disfatta epocale per il calcio italiano. Un complessivo impoverimento del movimento calcistico, come anche quello sociale che negli ultimi anni di crisi ha contribuito ulteriormente ad impoverire uno spettacolo tutto, che ha portato la nazionale ad essere composta per di più da giocatori di squadre non di vertice. Già, qualcuno dice che anche una squadra di serie B avrebbe sconfitto la Nuova Zelanda, ma io non credo che una squadra di serie B con la pressione che aveva addosso questa nazionale avrebbe saputo far di meglio. Anche perchè meglio del 2006 era difficile e la commistione tra attese e presunzioni dopo quel trionfo hanno generato un mix deleterio per le gambe e la testa degli azzurri. CT in primis.
Poi c'è la parte persino più abbietta, se possibile, dei tifosi che fazionano tutto. Lippi è il CT dell'Italia quando vince, uno sporco juventino quando perde. Difficile trovare qualcosa di più abbietto ed idiota di questa definizione. questo perchè se il tifoso della nazionale perde ce ne deve essere uno che vince: quello della squadra di club. Quello degli esclusi Balotelli e Cassano ad esempio (si badi bene: gente che faticava a giocare anche nelle proprie squadre di appartenenza). Quello del qualunquistico "tanto meglio tanto peggio". Ne ho sentite tante in questi giorni da riempire un libro... di fesserie. Ognuno è libero di essere il CT della nazionale durante il mondiale e fare le considerazioni e le proprie scelte, resta il fatto che la coerenza è merce rara ancor di più ai mondiali. L'unica virtù che Lippi ha certamente mantenuto da sempre e che in questo paese di parassiti e opportunisti è spesso considerata più una debolezza.
Ma ora bisogna guardare al futuro con un allenatore di sicuro valore e di grandi capacità, che comunque non è certo un vincente visto che in carriera non ha mai raggiunto obbiettivi particolarmente elevati. Bisogna affidarci un pò a lui e magari per il 2014 sfruttare l'elemento inverso: "Peggio del 2010 è impossibile!"... forse, almeno fino a quando non saremo con il biglietto aereo per il Brasile.