MSDN Magazine > Home > Issues > 2008 > Luglio >  Data Services: Sviluppare applicazioni solide e...
Data Services
Sviluppare applicazioni solide e scalabili con SQL Server Data Services
David Robinson

In questo articolo verranno discussi i seguenti argomenti:
  • Modello di dati SSDS
  • Gestione di entità, contenitori e autorità
  • Creazione di un'applicazione Web di esempio
  • Serializzazione e deserializzazione delle classi
In questo articolo verranno utilizzate le seguenti tecnologie:
SQL Server
Questo articolo si basa su una versione preliminare di SQL Server Data Services. Tutte le informazioni qui contenute sono soggette a modifica.
La piattaforma dati di Microsoft è composta da una ricca serie di prodotti e tecnologie per tutti i tipi di dati - dai dati binari non strutturati a un'estremità dello spettro, fino ai cubi OLAP (OnLine Analytical Processing) a elevata strutturazione dall'altra. Sebbene queste tecnologie possano essere applicate a qualsiasi scenario di applicazioni, in genere richiedono un investimento iniziale di risorse di tempo e hardware prima di essere in grado iniziare a codificare una soluzione.
Per gli sviluppatori di applicazioni, spesso è difficile operare una stima dei requisiti di archivio ed elaborazione richiesti da un'applicazione nell'arco del suo ciclo di vita. Il problema può presentarsi in maniera particolarmente acuta nel caso di un'azienda agli inizi, ovvero quando scarseggiano tanto i fondi quanto i dati da utilizzare a supporto delle decisioni sugli investimenti in hardware. Per dare una risposta a queste preoccupazioni circa gli investimenti iniziali, recentemente Microsoft ha aggiunto una nuova tecnologia alla sua già nutrita piattaforma dati: SQL Server® Data Services (SSDS).
Prodotto che non rientra nella tipica categoria dei software confezionati, SSDS è un robusto servizio dati senza problemi di scalabilità che utilizza internamente la collaudata tecnologia SQL Server ed espone la sua funzionalità nelle interfacce dei servizi Web standard del settore. SSDS fornisce un modello di dati flessibile e facile da utilizzare, al quale si accede tramite i protocolli aperti standard del settore.
SSDS può essere utilizzato come archivio dati indipendentemente dalle tecnologie adottate per lo sviluppo - Microsoft® .NET Framework, Java o altre ancora. Inoltre, SSDS è stato ideato per consentire agli sviluppatori di effettuare rapidamente il provisioning di un account e di iniziare immediatamente l'attività di sviluppo correlata.
SSDS fornisce agli sviluppatori un back-end dati che non è soggetto alle limitazioni imposte dalla disponibilità di alloggiamenti disco o spazio nel rack. Utilizzando SSDS come piattaforma dati, l'applicazione è libera di consumare tutti i dati di cui necessita. Indipendentemente dalla quantità, sia un gigabyte o un petabyte di archivio, il prezzo da pagare è determinato soltanto dalle risorse consumate.
Molte applicazioni e siti Web hanno modelli di accesso ciclico nei quali è necessario disporre di capacità sufficiente per i picchi di carico, benché tali carichi non vengano sostenuti. L'uso di SSDS come back-end dati lascia liberi di concentrarsi sull'applicazione da sviluppare, sollevati dalla necessità di dover pianificare la capacità della piattaforma dati.
In questo articolo illustrerò i principi fondamentali dello sviluppo di una soluzione dati basata su SSDS. Inizierò con la descrizione del modello di dati impiegato da SSDS. Quindi passerò ad approfondire i dettagli relativi allo sviluppo mostrando il modo in cui, avvalendomi di SSDS, ho creato il semplice sistema di annunci economici online mostrato nella Figura 1.
Figura 1 Applicazione Classifieds di esempio ospitata da SSDS (fare clic sull'immagine per ingrandirla)

Modello di dati SSDS
SSDS fornisce un flessibile modello di dati basato sulle entità. Il modello è composto dai tre elementi autorità, contenitore ed entità, che vengono chiamati concetti ACE (vedere la Figura 2). Un'autorità SSDS può essere correlata a un database nel mondo relazionale. Quando si effettua il provisioning di un'autorità, SSDS crea un nome DNS per l'utente affinché possa accedere all'autorità. Ad esempio, se si effettua il provisioning dell'autorità ssdsdemo, vi si accederà al seguente URI:
ssdsdemo.data.beta.mssds.com
Figura 2 Componenti SSDS di base
Un'autorità è anche l'unità di posizionamento geografico, ovvero il nome DNS creato eseguirà il mapping allo specifico centro dati di Microsoft in cui risiedono i dati. Se si creano le due autorità americasdemo.data.beta.mssds.com e europedemo.data.beta.mssds.com, ognuna di esse verrà associata a un centro dati diverso ubicato nel posto più vicino agli utenti che le useranno.
Un'autorità contiene una raccolta di contenitori, dove un contenitore è qualcosa di simile a una tabella in un database relazionale. La differenza principale è che a una tabella di database si deve associare uno schema per rendere omogenee tutte le righe della tabella, mentre un contenitore in SSDS non richiede l'associazione di uno schema. Questo consente di archiviare al suo interno entità eterogenee in una posizione comoda. Non è altro che una raccolta di entità. Nella versione corrente di SSDS, tutte le query hanno come ambito un unico contenitore.
Un altro punto importante da sottolineare è che ogni contenitore è posizionato in un nodo diverso del cluster SSDS. Per ottenere prestazioni maggiori, in SQL Server si posizionavano le diverse tabelle in assi separati per massimizzare le funzionalità di lettura/scrittura. In SSDS avviene qualcosa di simile, con la differenza che ogni contenitore si trova in un computer separato. Ulteriori miglioramenti delle prestazioni possono essere ottenuti con il partizionamento dei dati in più contenitori e il multithreading delle richieste, poiché in questo modo le operazioni di lettura e scrittura non saranno soggette ai limiti imposti da un unico computer.
Una nota finale sui contenitori: mentre ogni contenitore si trova in un nodo separato del cluster SSDS, anche i dati sono replicati in diversi altri nodi a scopo di ripristino di emergenza. Se si verifica un malfunzionamento del computer in cui si trova un contenitore, una delle repliche di backup viene automaticamente promossa per assicurare che l'applicazione non subisca una perdita di dati o prestazioni.
Si potrebbe paragonare un'entità alla riga di una tabella in un database relazionale. Un'entità è soltanto un contenitore delle proprietà delle coppie nome/valore. Le coppie nome/valore sono raggruppate in due categorie: proprietà di sistema distinte e proprietà flessibili.
Le proprietà di sistema distinte sono comuni a tutte le entità e comprendono ID, Kind e Version. L'ID identifica l'entità in modo univoco. Gli ID devono essere univoci all'interno del contenitore in cui esistono, tuttavia contenitori diversi possono ospitare entità con lo stesso ID. Per riunire in categorie le entità simili viene utilizzata la proprietà Kind. All'entità non è associato alcuno schema, perciò avere entità con la stessa proprietà Kind non garantisce identità di struttura, mentre la proprietà Version è utilizzata per identificare la versione corrente dell'entità. È un valore che viene aggiornato a ogni operazione.
Nelle proprietà flessibili lo sviluppatore archivia i dati dell'applicazione. Le proprietà flessibili supportano tipi di dati semplici: string, decimal, bool, datetime e binary. Ognuna di esse è indicizzata fino ai primi 256 byte.

Creazione di un sistema di annunci economici
Per dare una dimostrazione delle funzionalità di SSDS e fornire una panoramica dell'esperienza di sviluppo dell'applicazione, descriverò in dettaglio l'implementazione di Contoso Classifieds, un sistema di annunci economici online di esempio. L'esempio è composto da due applicazioni separate: un'applicazione di Windows® Forms per l'amministrazione del sistema e un'applicazione ASP.NET, il principale sito di Contoso Classifieds, che sarebbe quella visitata dagli utenti. L'applicazione accede a SSDS tramite un'interfaccia SOAP nell'applicazione di Windows Forms e tramite l'interfaccia REST (Representational State Transfer) all'interno dell'applicazione ASP.NET.
Al momento della registrazione per ottenere un account SSDS, si riceve un nome utente e una password. Da quel punto in avanti, dipende dallo sviluppatore iniziare a creare autorità, contenitori ed entità. Nel caso di Contoso Classifieds, ho creato l'autorità contosoclassifieds, mentre data.data.beta.mssds.com è l'endpoint URI del servizio nella versione beta di SSDS.
L'autorità contosoclassifieds include due contenitori: Categories e Cities. Categories contiene le entità relative alle varie categorie Listing. Cities contiene le entità per ogni città definita nel sistema. Le entità sono soltanto puntatori per le autorità specifiche delle città. Ogni autorità specifica di città ha un contenitore per ogni categoria Listing (vedere la Figura 3).
Figura 3 Elementi di Contoso Classifieds (fare clic sull'immagine per ingrandirla)
Alla base di questo progetto si sono diverse ragioni. Da una parte, ho scelto un'implementazione incentrata sulle autorità per città perché così posso fare in modo che l'autorità ottenga il provisioning in un centro dati che sia geograficamente vicino alla popolazione servita. Dall'altra, ho scelto di utilizzare un contenitore per categoria Listing così da avere un modello di query semplice e posso distribuire il carico in più computer del cluster (tenere presente che un contenitore ha come ambito un nodo specifico nel cluster back-end). La Figura 4 mostra l'interfaccia utente del client di amministrazione di Contoso Classifieds.
Figura 4 Client di amministrazione dell'applicazione Classifieds (fare clic sull'immagine per ingrandirla)
Come detto in precedenza, la creazione di autorità, contenitori ed entità utilizzati dall'applicazione dipende dallo sviluppatore. Nell'applicazione di esempio, tutto questo lavoro è contenuto nell'evento clic del pulsante Perform Initial Setup. (Il codice completo, compresa la gestione degli errori, è incluso nel download di codice di questo articolo).
Effettuare il provisioning di un'autorità SSDS è estremamente facile. Nel codice di esempio, ho utilizzato SitkaSoapServiceClient, nel quale avevo in precedenza aggiunto all'interfaccia SOAP di SSDS un riferimento al servizio:
using (SSDSClient.SitkaSoapServiceClient ssdsProxy = 
  new SSDSClient.SitkaSoapServiceClient())
Un'autorità è qualcosa di già noto, ma cos'è un ambito SSDS? All'interno di SSDS, l'oggetto ambito viene utilizzato per fornire un modo di assicurare l'indirizzamento agli oggetti nel servizio SOAP, in un modo del tutto simile a quello in cui si usano gli URI nel servizio REST.
La prima cosa da fare è impostare il nome utente e la password del servizio:
ssdsProxy.ClientCredentials.UserName.UserName = 
  txtUserName.Text;
ssdsProxy.ClientCredentials.UserName.Password = 
  txtPassword.Text;
Poiché occorre creare un'autorità che sia al livello più elevato del modello ACE, si deve innanzitutto creare un ambito vuoto:
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
Ora si deve creare l'autorità Contoso Classifieds. Conterrà le informazioni di configurazione generale del sistema:
SSDSClient.Authority contosoAuth = 
  new SSDSClient.Authority();
contosoAuth.Id = "contosoclassifieds";
Inviare la creazione a SSDS:
ssdsProxy.Create(serviceScope, contosoAuth);
Una volta creata la principale autorità Contoso Classifieds, associare all'ambito il puntatore per tale autorità, quindi creare il contenitore che conterrà le città a cui fornire gli annunci economici, infine inviare la creazione del contenitore a SSDS:
serviceScope.AuthorityId = contosoAuth.Id;

SSDSClient.Container citiesContainer = new SSDSClient.Container();
citiesContainer.Id = "Cities";

ssdsProxy.Create(serviceScope, citiesContainer);
Il processo per la creazione di un contenitore è simile a quello per la creazione di un'autorità. L'unica differenza è che si crea un oggetto Container anziché un oggetto Authority e, anziché l'ambito vuoto, si aggiorna l'ambito in modo che punti all'autorità in cui deve essere creato il contenitore.
Creare il contenitore che conterrà le categorie Header e Listing da supportare all'interno dell'applicazione:
SSDSClient.Container categoriesContainer = 
  new SSDSClient.Container();
categoriesContainer.Id = "Categories";

ssdsProxy.Create(serviceScope, categoriesContainer);
Una volta creata, si dovrà associare al browser Web il puntamento all'autorità utilizzando HTTP o HTTPS e visualizzare il contenuto del contenitore usando l'interfaccia REST. È sufficiente utilizzare il nuovo nome DNS creato da SSDS al momento dell'impostazione dell'autorità. In questo caso, l'URL sarebbe:
http://contosoclassifieds.data.data.beta.mssds.com/v1
Dopo aver immesso l'URL nel browser, viene richiesto di eseguire l'autenticazione. Una volta riuscita l'autenticazione, nel browser è visualizzato qualcosa di simile a quanto segue:
<s:Authority xmlns:s="http://schemas.microsoft.com/sitka/2008/03/" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:x="http://www.w3.org/2001/XMLSchema">
  <s:Id>contosoclassifieds</s:Id>
  <s:Version>11</s:Id>
</s:Authority>
Dal momento che SSDS crea una voce DNS per l'autorità, il nome dell'autorità è in minuscolo perché si devono rispettare le convenzione di denominazione DNS.
Dopo la creazione del contenitore, se si aggiorna il browser e si aggiunge una query vuota all'URL nel formato ?q="", in EntitySet verrà restituito un solo oggetto Container. Un EntitySet non è altro che una raccolta di entità restituite in risposta a una query. Al termine della configurazione iniziale, l'applicazione di esempio avrà creato un'autorità (contosoclassifieds) e due contenitori (Categories e Cities):
<s:EntitySet xmlns:s="http://schemas.microsoft.com/sitka/2008/03/" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:x="http://www.w3.org/2001/XMLSchema">
  <s:Container>
    <s:Id>Categories</s:Id>
    <s:Version>1</s:Id>
  </s:Container>
  <s:Container>
    <s:Id>Cities</s:Id>
    <s:Version>1</s:Id>
  </s:Container>
</s:EntitySet>
Ora che la configurazione iniziale è terminata, andiamo avanti e aggiungiamo la funzionalità delle città servite.

Aggiunta di una città
Come accennato sopra, Contoso Classifieds colloca gli elenchi di ogni città nella propria autorità, cosa che consente di scegliere il centro dati in cui è ospitata. (Benché questa funzione non sia attualmente supportata nella versione beta di SSDS, lo sarà al momento del rilascio del prodotto).
Il codice per l'aggiunta della città è simile a quello utilizzato per la configurazione iniziale. Utilizzando il proxy SOAP, si devono impostare le credenziali, creare un ambito vuoto, impostare l'ID autorità e inviare il codice creato:
SSDSClient.Authority cityAuth = 
  new SSDSClient.Authority();
cityAuth.Id = cityAuthorityName;

ssdsProxy.Create(serviceScope, cityAuth);
Ora che la nuova autorità della città è stata creata, aggiungere un'entità puntatore al contenitore Cities nell'autorità principale contosoclassifieds:
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Cities";

//Create the City Entity, and set its properties appropriately
SSDSClient.Entity cityEntity = new SSDSClient.Entity();
cityEntity.Id = cityAuthorityName;
cityEntity.Kind = "CityServed";
cityEntity.Properties = new Dictionary<string, object>();
cityEntity.Properties["AuthorityUri"] = 
  string.Format("{0}.data.data.beta.mssds.com/v1/", cityAuthorityName);
cityEntity.Properties["Name"] = txtCity.Text;

//Issue the create to SSDS
ssdsProxy.Create(serviceScope, cityEntity);
Facciamo un breve riepilogo. Ho creato l'autorità principale contosoclassifieds che contiene tutti i dati dell'intero sistema. In quell'autorità, è presente il contenitore Cities che dispone di un'entità per ogni città servita dall'applicazione. L'entità contiene il nome visualizzato della città, insieme a un puntatore all'autorità contenente tutti gli elenchi.

Aggiunta di categorie
Fino a questo punto, le operazioni di aggiunta hanno riguardato soltanto autorità, contenitori ed entità. Il passaggio successivo consiste nell'implementare la funzionalità delle categorie di elenco. A questo scopo, sarà necessario servirsi delle funzionalità di query, aggiornamento ed eliminazione di SSDS.
Contoso Classifieds supporta un'intestazione di categoria, con sottocategorie per contenere i post reali degli utenti. Se per questi dati si dovesse adottare un modello relazionale, solitamente si userebbe il modello intestazione/riga. Tuttavia, il modello di dati con entità flessibili utilizzato da SSDS consente ai dati di assumere la forma preferita dallo sviluppatore. Nel caso dell'applicazione Contoso Classifieds, ho scelto un'unica entità per rappresentare ogni intestazione di categoria e tutte le sottocategorie che le appartengono (vedere la Figura 5).
Figura 5 Modello di dati con entità utilizzato dall'applicazione (fare clic sull'immagine per ingrandirla)
È interessante notare che mi servo di questo modello soltanto per evidenziare la natura flessibile di un'entità e per dimostrare che, sebbene più entità possano avere la stessa proprietà Kind, questo non obbliga le entità a essere omogenee. L'entità può avere qualsiasi forma scelta dallo sviluppatore.
Un approccio migliore a questo scenario potrebbe essere quello di avere due proprietà Kind diverse per le entità, Category e Listing Category, e di avere la proprietà flessibile CategoryID in tutte le entità. Ciò permetterebbe di inviare una query come la seguente:
from e in entities where e["CategoryID"] ==­    "For Sale" select e
Notare che ho detto proprietà flessibile. Come ricordato in precedenza, le entità in un contenitore devono avere ID univoci, cosa che impedisce di utilizzare la proprietà di sistema distinta ID. Notare anche la sintassi della query che ho creato. Per le query, SSDS utilizza una sintassi simile a LINQ, con cui la maggior parte degli sviluppatori che usano .NET Framework dovrebbe avere una certa familiarità.
Se si osserva l'applicazione di amministrazione mostrata nella Figura 4, si possono notare una visualizzazione ad albero che rappresenta le categorie di elenco e due caselle di testo per l'aggiunta di intestazioni di categoria e categorie di elenco. Occupiamoci allora dell'implementazione a monte dell'aggiunta di un'intestazione di categoria.
Come nel caso del codice riportato sopra, si deve utilizzare SitkaSoapServiceClient, impostare le credenziali e impostare l'ambito sull'autorità contosoclassifieds e il contenitore Categories. Tutto ciò che resta da fare è creare l'entità:
SSDSClient.Scope serviceScope = new SSDSClient.Scope();

serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";

SSDSClient.Entity categoryHeaderEntity = new SSDSClient.Entity();
categoryHeaderEntity.Id = CategoryID;
categoryHeaderEntity.Kind = "Category";
Si può anche aggiungere una sola proprietà flessibile che contenga il nome della categoria. Questo aggiungerà soltanto un'unica proprietà alla raccolta di proprietà flessibili:
categoryHeaderEntity.Properties = 
  new Dictionary<string, object>();
categoryHeaderEntity.Properties["CategoryName"] = CategoryName;
Il passaggio successivo consiste nell'aggiungere le categorie di elenco. Poiché Contoso Classifieds usa una sola entità per rappresentare l'intera raccolta di categorie di elenco all'interno di un'intestazione di categoria, si deve richiamare l'entità di categoria, aggiornare le altre categorie di elenco (che saranno aggiunte come ulteriori proprietà flessibili) e inviare l'aggiornamento a SSDS.
Per recuperare l'entità di categoria, impostare l'ambito in modo che punti all'entità e chiamare il metodo Get, che recupererà l'entità direttamente senza dover inviare una query:
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";
serviceScope.EntityId = CategoryID;
//Retrieve the Category Entity
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
Ora è sufficiente determinare l'indice della nuova proprietà, aggiungerlo come ulteriore proprietà flessibile e inviare l'aggiornamento (vedere la Figura 6).
//Determine whether this is the first listing
//category being added to this header
if (categoryEntity.Properties.Count > 2) {
  propCount = ((categoryEntity.Properties.Count - 1) / 2);
  propCount++;
}
else {
  propCount = 1;
}

//Create the FlexProperties for the new listing category
string listingIdPropName = string.Format  ("ListingCategoryID{0}", propCount);
string listingNamePropName = string.Format  ("ListingCategoryName{0}", propCount);

categoryEntity.Properties[listingIdPropName] = ListingCategoryID;
categoryEntity.Properties[listingNamePropName] = ListingCategoryName;

//Issue the update to SSDS
ssdsProxy.Update(serviceScope, categoryEntity);

Aggiornamento ed eliminazione di entità
L'aggiornamento delle categorie di elenco è altrettanto semplice dell'aggiunta di un'ulteriore proprietà flessibile. Per ogni aggiornamento, è necessario recuperare l'entità, eseguire gli aggiornamenti localmente e inviare l'aggiornamento, passando al metodo Update sia l'entità aggiornata che l'ambito.
Per iniziare, impostare l'ambito del servizio sull'autorità principale contosoclassifieds. Inoltre, associarvi il puntamento al contenitore Categories e all'entità della categoria da aggiornare:
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";
serviceScope.EntityId = currentHdrNode.Name;
Chiamare l'entità che si deve aggiornare:
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
Ora reinizializzare la raccolta di proprietà flessibili, aggiungendo di nuovo tutte le proprietà flessibili delle categorie di elenco. In alternativa, era possibile anche eseguire il ciclo continuo per ogni proprietà flessibile e apportare gli aggiornamenti ma, vista la struttura semplice di questa entità, è più facile ricrearla nel modo mostrato nella Figura 7.
categoryEntity.Properties = new Dictionary<string, object>();
categoryEntity.Properties["CategoryName"] = currentHdrNode.Text;

int propCount = 1;

if (e.Node.Parent != null) {
  //Loop through each node and add its category ID and
  //category name flex property.
  foreach (TreeNode node in e.Node.Parent.Nodes) {
    string listingIdPropName = string.Format      ("ListingCategoryID{0}", propCount);
    string listingNamePropName = string.Format      ("ListingCategoryName{0}", propCount);

    categoryEntity.Properties[listingIdPropName] = node.Name;
    categoryEntity.Properties[listingNamePropName] = node.Text;

    //if we are re-adding the updated category, the tree view 
    //will still have its old value, so set the flex property
    //to the updated value
    if (node.Name == e.Node.Name) {
      categoryEntity.Properties[listingIdPropName] =         m_OldSelectNode.Name;
      categoryEntity.Properties[listingNamePropName] = e.Label;
    }

    propCount++;
  }
}

//Submit the update to SSDS
ssdsProxy.Update(serviceScope, categoryEntity);
Per eliminare un'entità o un contenitore, puntare ServiceScope sull'elemento e chiamare il metodo Delete di SitkaSoapServiceClient. Se è un'intestazione, eliminare l'intera entità:
if (m_OldSelectNode.Parent == null) {
  ssdsProxy.Delete(serviceScope);
}
Per le altre entità, proprio come avvenuto con l'aggiornamento, ricreare la raccolta di proprietà flessibili e ricompilare, eseguendo l'aggiunta di tutti i nodi tranne quello eliminato:
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
categoryEntity.Properties = new Dictionary<string, object>();
categoryEntity.Properties["CategoryName"] = CategoryName;

int propCount = 1;

foreach (TreeNode node in entityNode.Nodes) {
  string listingIdPropName = string.Format    ("ListingCategoryID{0}", propCount);
  string listingNamePropName = string.Format    ("ListingCategoryName{0}", propCount);

  if (node.Text != m_OldSelectNode.Text) {
    categoryEntity.Properties[listingIdPropName] = node.Name;
    categoryEntity.Properties[listingNamePropName] = node.Text;

    propCount++;
  }
}
Infine, inviare l'aggiornamento a SSDS e rimuovere il nodo dalla visualizzazione ad albero:
ssdsProxy.Update(serviceScope, categoryEntity);

tvCategories.Nodes.Remove(m_OldSelectNode);

Aggiunta ed eliminazione di schemi di elenco
La versione beta corrente di SSDS non supporta gli schemi. In futuro è prevista l'integrazione di questo supporto, tuttavia l'aggiunta di schemi all'interno di SSDS è un'attività facile. L'unica avvertenza è che si deve gestire lo schema dall'interno dell'applicazione poiché, al momento, SSDS non lo applica.
Nel caso di Contoso Classifieds, all'amministratore è sembrata una buona idea definire uno schema personalizzato per alcune categorie di elenco. Lo schema può essere definito all'interno del client di amministrazione e utilizzato successivamente al momento dell'implementazione del sito Web.
Fino a questo momento, non ci sono entità eterogenee all'interno di uno stesso contenitore. Poiché per ogni entità è definita una proprietà Kind, la si può usare per archiviare tipi di entità diversi nello stesso contenitore e anche per eseguire query sulle entità di un determinato tipo o, in questo caso, di tipo Kind. Sebbene non ci sia alcun contratto di schema associato a Kind, è facile impiegare questo meccanismo per raggruppare dati simili. Ora aggiungerò al contenitore Categories una nuova entità la cui proprietà Kind è ListingSchema.
La finestra di dialogo per l'aggiunta di uno schema di elenco è piuttosto semplice, come mostrato nella figura Figura 8. Consente all'amministratore di definire i campi aggiuntivi associati a un elenco e di identificarne alcuni come campi obbligatori. Aggiungerò altri dettagli più avanti, quando descriverò come implementare il sito Web vero e proprio.
Figura 8 Personalizzazione di uno schema per un elenco (fare clic sull'immagine per ingrandirla)
Il processo adottato per aggiungere l'entità ListingSchema è lo stesso visto in precedenza, ma stavolta si utilizza un'entità diversa (Kind). Il codice per l'aggiunta di ListingSchema è mostrato nella Figura 9. Dopo aver aggiunto lo schema di elenco per la categoria For Sale Cars, notare che all'interno del contenitore Categories sono presenti due tipi diversi di entità, con dati totalmente differenti nello stesso contenitore.
listingSchemaEntity.Kind = "ListingSchema";
listingSchemaEntity.Properties = new Dictionary<string, object>();
listingSchemaEntity.Properties["ListingID"] = m_ListingID;
listingSchemaEntity.Properties["ListingName"] = m_ListingName;

int propCount = 0;
bool required = false;

foreach (DataGridViewRow row in dataGridView1.Rows) {
  if (row.IsNewRow) continue;
  propCount++;

  listingSchemaEntity.Properties[string.Format    ("Property{0}Name",propCount)] = 
    row.Cells["colFieldName"].Value.ToString();
  listingSchemaEntity.Properties[string.Format    ("Property{0}DataType", propCount)] = 
    row.Cells["colDataType"].Value.ToString();

  if (row.Cells["colRequired"].Value == null) {
    required = false;
  }
  else {
    required = Convert.ToBoolean(row.Cells      ["colRequired"].Value.ToString());
  }

  listingSchemaEntity.Properties[string.Format    ("Property{0}Required", propCount)] = required;
}

if (propCount > 0) {
  using (SSDSClient.SitkaSoapServiceClient ssdsProxy =     new SSDSClient.SitkaSoapServiceClient()) {
    //Set username and password for the service
    ...

    //Set service scope to the main contoso classifieds
    //authority. Point it to the categories container
    ...

    //If this entity doesn't exist, create it
    if (listingSchemaID == "" || listingSchemaID == null) {
      ssdsProxy.Create(serviceScope, listingSchemaEntity);
    }
    //Otherwise update it
    else {
      serviceScope.EntityId = listingSchemaID;
      ssdsProxy.Update(serviceScope, listingSchemaEntity);
    }
  }
  MessageBox.Show("Custom Schema Saved", "Custom Schema Saved",
    MessageBoxButtons.OK, MessageBoxIcon.Information);
  this.Hide();
}
L'ultima parte del client di amministrazione serve a eliminare uno schema di elenco personalizzato quando si elimina una categoria di elenco. In precedenza ho descritto come eliminare entità e contenitori, ma in quei casi conoscevo l'ID dell'entità o del contenitore da eliminare. Eliminare uno schema significa per prima cosa inviare una query LINQ per recuperare tutti gli schemi di elenco associati alle categorie di elenco ed eliminarli. La query è simile all'esempio seguente:
string.Format(@"from e in entities 
  where e.Id == ""{0}"" select e", CategoryID);
La query restituisce un elenco di entità ListingSchema e non resta che eliminarle. L'intero metodo DeleteListingSchema è mostrato nella Figura 10.
//Retrieve the custom schema entity for this listing
string linqQuery =   string.Format(
  @"from e in entities where e.Id == ""{0}"" select e", CategoryID);

List<SSDSClient.Entity> entities =
  ssdsProxy.Query(serviceScope, linqQuery);

foreach (SSDSClient.Entity entity in entities) {

  //If more than 1 flex property on the entity, the
  //header has listing categories attached to it.
  if (entity.Properties.Count > 1) {
    //Calculate the number of Listing Categories
    int propCount = ((entity.Properties.Count - 1) / 2);
    for (int x = 1; x < propCount + 1; x++) {
      //Delete the listing schema for each listing
      //in this category
      DeleteListingSchema(entity.Properties["ListingCategoryID" + 
        x.ToString()].ToString());
    }
  }
}
A questo punto il client di amministrazione Contoso Classifieds è stato completato e posso passare a illustrare l'implementazione del sito Web utilizzando l'interfaccia REST di SSDS.

Applicazione Web Classifieds
Come ho spiegato in precedenza, si può accedere a SSDS sia tramite la sua interfaccia REST sia attraverso l'interfaccia SOAP (come ho fatto con il client di amministrazione Contoso Classifieds). I concetti e le funzionalità sono uguali, indipendentemente dall'interfaccia scelta, ma REST consente di eseguire chiamate HTTP (o HTTPS) dirette.
Per iniziare, è bene osservare nuovamente l'interfaccia principale dell'applicazione Web mostrata nella Figura 1. Si vedono una visualizzazione ad albero che riporta le categorie di elenco, un'area principale del contenuto e un controllo DataList con tutte le città che attualmente l'applicazione è configurata per supportare. Analizzando ognuno di questi elementi, mostrerò quanto sia facile sviluppare un'applicazione ASP.NET basata su SSDS.
Se si esamina il codice ASPX della visualizzazione ad albero, si scopre che è piuttosto semplice:
<h3>Groups</h3>
<asp:TreeView ID="TreeView1" runat="server" 
  onselectednodechanged="TreeView1_SelectedNodeChanged">
</asp:TreeView>
La magia avviene nel code behind, come si può vedere nella Figura 11. La prima cosa di cui occuparsi è la creazione dell'URI, che è il puntatore al contenitore Categories. Poiché i dati non riguardano una città specifica, ho scelto di archiviarli nell'autorità principale contosoclassifieds, all'interno del contenitore Categories.
TreeView1.Nodes.Clear();

appDataUri = string.Format(@"http://{0}.{1}{2}", 
  conSSDSAuthName, conSSDSUri, "Categories");
query = @"from e in entities where e.Kind == ""Category"" select e";

UriBuilder newUri = new UriBuilder(appDataUri);
newUri.Query = String.Format("q='{0}'", Uri.EscapeDataString(query));

string xmlResults = 
  HTTPHelper.GetHTTPWebRequest(newUri.Uri.ToString(), 
  new System.Net.NetworkCredential(conSSDSUsername, conSSDSPassword));

XmlDocument categoriesDoc = new XmlDocument();
categoriesDoc.LoadXml(xmlResults);
XmlNodeList nodeList = categoriesDoc.SelectNodes("//Category");

int nodeIndex = 0;

foreach (XmlNode node in nodeList) {
  if (node.ChildNodes.Count > 3) {
    int propCount = ((node.ChildNodes.Count - 1) / 2);

    TreeNode tn = new TreeNode(node.ChildNodes[2].InnerText, 
      node.ChildNodes[0].InnerText);
    TreeView1.Nodes.Add(tn);

    for(int x=1;x<propCount;x++) {
      tn = new TreeNode(node.ChildNodes[(x*2)+2].InnerText, 
        node.ChildNodes[(x*2)+1].InnerText);
      TreeView1.Nodes[nodeIndex].ChildNodes.Add(tn);
    }      
  }
  else {
    TreeNode tn = new TreeNode(node.ChildNodes[2].InnerText,       node.ChildNodes[0].InnerText);
    TreeView1.Nodes.Add(tn);
  }

  nodeIndex++;
}
In seguito, mi servo di una query LINQ per recuperare tutte le entità con la categoria Kind e quindi utilizzo un oggetto UriBuilder per riunire tutto e aggiungere il necessario escaping di HTTP . Quindi, proseguo e chiamo il metodo GetHTTPWebRequest, passando l'URI con escape e un oggetto NetworkCredential con il nome utente e la password di SSDS. GetHTTPWebRequest restituisce una rappresentazione di stringa di un EntitySet con tutte le entità che soddisfano i predicati della query.
GetHTTPWebRequest è un metodo helper statico per nascondere alcuni dei dettagli di elaborazione HTTP:
WebRequest request = 
  HttpWebRequest.Create(Uri.EscapeUriString(serviceUri));
request.Credentials = requestCredential;
request.Method = "GET";
request.ContentType = XmlContentType;

// Get the response and read it in to a string.
using (HttpWebResponse response = 
  (HttpWebResponse)request.GetResponse()) {

  return ReadResponse(response);
}
Ora non resta altro da fare che creare un nuovo oggetto WebRequestbject, impostare le credenziali che gli sono state passate, impostare il verbo HTTP appropriato, impostare ContentType su XML e chiamare il metodo GetResponse. Quindi chiamo ReadResponse, che è un altro metodo helper statico che usa un lettore di flussi per leggere HTTPResponse e restituirlo al chiamante. Infine, il code behind prende l'XML restituito, lo carica in un documento XmlDocument e infine carica la visualizzazione ad albero.

Deserializzazione delle classi
Fin qui, il codice ha soltanto manipolato manualmente l'XML, ma è facile prendere un'entità e deserializzarla in una classe. In questo caso, l'applicazione utilizza la classe CityServed.
La classe Entity di base è piuttosto elementare. Contiene le proprietà sia di ID sia di Version, che sono comuni a tutte le entità. Contiene anche gli attributi necessari per rendere serializzabile la classe.
La classe CityServed, mostrata nella Figura 12, eredita la classe Entity di base. Un metodo Query generico restituisce un elenco di entità di tipo CityServed:
foreach (CityServed i in HTTPHelper.Query<CityServed>(
  appDataUri, query, new System.Net.NetworkCredential(
  conSSDSUsername, conSSDSPassword)))
[XmlRoot(ElementName = "CityServed", Namespace = "")]

public class CityServed : Entity {
  [XmlElement(ElementName = "AuthorityUri")]
  public object AuthorityUriField;

  [XmlElement(ElementName = "Name")]
  public object NameField;

  public override string ToString() {
    return Name;
  }

  [XmlIgnore]
  public string AuthorityUri {
    get { return (string)AuthorityUriField; }
    set { AuthorityUriField = value; }
  }

  [XmlIgnore]
  public string Name {
    get { return (string)NameField; }
    set { NameField = value; }
  }
}
Il metodo Query, incluso nel download di codice di questo articolo, non è altro che un generico metodo helper statico che chiama lo stesso metodo GetHTTPWebRequest già usato in precedenza. I metodi Serialize e Deserialize sono mostrati nella Figura 13. Il metodo Query, usato in combinazione con i metodi Serialize e Deserialize, offre la possibilità di utilizzare oggetti .NET fortemente tipizzati e di salvarli in SSDS.
private static T Deserialize<T>(Stream stm, string xmlPayload) {
  XmlSerializer ser = new XmlSerializer(typeof(T));

  T flex = (T)ser.Deserialize(stm);

  XmlDocument xDom = new XmlDocument();
  xDom.LoadXml(xmlPayload);

  return flex;
}

public static T Deserialize<T>(String xmlPayload) {
  using (MemoryStream stm = new MemoryStream()) {
    Encoding encoding = new UTF8Encoding(false);
    stm.Write(encoding.GetBytes(xmlPayload), 0, encoding.GetByteCount(xmlPayload));
    stm.Position = 0;
    return Deserialize<T>(stm, xmlPayload);
  }
}

public static string Serialize<T>(T flex) {
  using (MemoryStream stm = new MemoryStream()) {
    Serialize(stm, flex);

    stm.Position = 0;

    using (StreamReader reader = new StreamReader(stm)) {
      return reader.ReadToEnd();
    }
  }
}
private static void Serialize<T>(Stream stm, T flex) {
  XmlSerializer ser = new XmlSerializer(typeof(T));
  Encoding encoding = new UTF8Encoding(false);
  XmlWriterSettings settings = new XmlWriterSettings();
  settings.CloseOutput = false;
  settings.ConformanceLevel = ConformanceLevel.Document;
  settings.Encoding = encoding;
  settings.Indent = true;
  settings.OmitXmlDeclaration = true;

  using (XmlWriter writer = XmlWriter.Create(stm, settings)) {
    ser.Serialize(writer, flex);
  }
}

Uso dello schema di elenco personalizzato
Come ricordato all'inizio, le entità SSDS sono flessibili, ovvero ogni entità può avere la forma richiesta dal contesto. Il client di amministrazione Contoso Classifieds, ad esempio, offriva la possibilità di aggiungere uno schema a una categoria di elenco. È anche possibile estrarre da SSDS lo schema personalizzato e creare dinamicamente un modulo di immissione utilizzabile per aggiungere elenchi al sistema.
Il primo passaggio consiste nel creare una tabella DataTable con quattro colonne: Field, Value, DataType e Required. La tabella dovrà contenere i campi dell'elenco definiti dall'amministratore. Successivamente, eseguo una query per vedere se per l'elenco è stato definito uno schema personalizzato:
string appDataUri = string.Format(@"http://{0}.{1}{2}", 
  conSSDSAuthName, conSSDSUri, "Categories");
string query = string.Format(
  @"from e in entities where e[""ListingID""] == ""{0}"" select e", 
  listingCategoryID);

UriBuilder newUri = new UriBuilder(appDataUri);
newUri.Query = String.Format("q='{0}'", Uri.EscapeDataString(query));

string xmlResults = HTTPHelper.GetHTTPWebRequest(newUri.Uri.ToString(), 
  new System.Net.NetworkCredential(conSSDSUsername, conSSDSPassword));

XmlDocument categoriesDoc = new XmlDocument();
categoriesDoc.LoadXml(xmlResults);
XmlNodeList nodeList = categoriesDoc.SelectNodes("//ListingSchema");
Quindi posso caricare l'XML restituito in DataTable e associarlo a DataList.
Nell'interfaccia REST, si deve creare la rappresentazione XML dell'entità e inviare una richiesta HTTP POST al contenitore in cui inserire l'entità. Creare l'entità è facile: è sufficiente utilizzare un'entità standard e scandire ogni campo, aggiungendola come nodo nel documento XML:
foreach (DataListItem fieldItem in dlAddFields.Items) {
  Label lblField = (Label)fieldItem.FindControl("lblAddField");
  TextBox tbItem = (TextBox)fieldItem.FindControl("txtAddValue");
  Label lblFieldType = (Label)fieldItem.FindControl("lblAddFieldType");
  entity = entity + String.Format(
    @" <{0} xsi:type='x:{1}'>{2}</{0}>", lblField.Text.Replace(" ", ""), 
    lblFieldType.Text, tbItem.Text);
}
Infine, si deve creare l'URI che punta al contenitore in cui effettuare il posting e inviare il verbo POST:
string serviceUri = string.Format(@"http://{0}.{1}{2}", 
  postingAuthority, conSSDSUri, postingContainer);

HTTPHelper.PostHTTPWebRequest(serviceUri, entity, 
  new System.Net.NetworkCredential(
  conSSDSUsername, conSSDSPassword));

Novità future
È facile sviluppare applicazioni che utilizzino SSDS. Vorrei sottolineare un punto: SSDS è stato creato sulla base delle tecnologie collaudate SQL Server e Windows Server®. Sebbene il team abbia scelto di rivelare soltanto una parte dell'insieme di funzionalità della versione beta iniziale di SSDS, ne sono previste molte altre per il futuro. Il sottoinsieme di funzionalità disponibile al momento è sufficiente per fornire assistenza in molti degli scenari in cui i clienti si trovano a operare.
Inoltre, si prevede l'aggiunta del supporto SSDS agli altri prodotti della famiglia di prodotti e strumenti SQL Server. Con il suo supporto dei protocollo aperti, perciò, per gli sviluppatori che utilizzano C#, Visual Basic®, Java, Ruby o persino Microsoft Office Access®, SSDS è il posto ideale per archiviare i dati delle applicazioni. Per ulteriori informazioni, visita il sito SSDS all'indirizzo microsoft.com/sql/dataservices (in inglese) e registrati per la versione beta. Se vorresti che nel prodotto fossero incluse ulteriori funzionalità, scrivimi all'indirizzo david.robinson@microsoft.com.

David Robinson è Senior Program Manager nel team SQL Server Data Services di Microsoft, dove gran parte del suo tempo è dedicata ad aggiungere al prodotto nuove e interessanti funzionalità. Ama offrire presentazioni in occasione di eventi delle community e raccogliere commenti e suggerimenti su SSDS da parte degli utenti.

Page view tracker