Di Paolo De Nictolis
Con SQL Server 2008, già noto come Katmai, la Microsoft intende rilasciare una piattaforma per la gestione delle informazioni che, rispetto a SQL Server 2005, comprende una serie di miglioramenti in tema di sicurezza, gestione, prestazioni, business intelligence, reportistica, gestione dei dati destrutturati e, dal punto di vista dello sviluppatore, un approccio alle applicazioni che abbracci pienamente il paradigma object-oriented, eliminando il conflitto di impedenza fra linguaggi .NET ed accesso relazionale basato su SQL ai dati, grazie alle cosiddette entità di business (business entities) esposte dall’ ADO.NET Entity Framework .
La CTP (Community Technology Preview) di SQL Server 2008 è già disponibile per il download, previa registrazione al sito di sviluppo collaborativo Connect . Per le mie prove ho utilizzato la versione x86 della Developer Edition (il prodotto è disponibile anche per piattaforme x64), versione 10.00.1019.17, che consente di apprezzare tutte le caratteristiche funzionali, mentre per gli ambienti di produzione che richiedono caratteristiche di alta affidabilità, ridondanza, bilanciamento di carico, dovremo aspettare le opportune edizioni.
Il download è di quasi 900 MB; è possibile scaricare a parte i 120 MB dei BOL (Books On Line), che comprendono l’intera documentazione, ed è consigliabile procedere a scaricare anche l’ADO.NET Entity Framework e i relativi esempi. Con questa versione dovremo inoltre entrare nell’ordine di idee che i database ed il codice di esempio non saranno più rilasciati insieme al prodotto, ma saranno disponibili come progetti collaborativi agli indirizzi http://www.codeplex.com/MSFTDBProdSamples e http://www.codeplex.com/SqlServerSamples: al momento, un download di 25 MB.
Ho provveduto all’installazione sulla Beta 3 di Windows Server 2008, e per gli esempi di sviluppo ho utilizzato la Beta 1 delle versioni Express gratuite di Visual Studio 2008. L’installazione completa, più gli esempi, permettono di fare cifra tonda: arriviamo ad 1 GB di spazio occupato su disco. A titolo informativo, segnalo anche che è disponibile una overview di alto livello delle caratteristiche del prodotto.
In questa pagina
Installazione
Policy-based management
La piattaforma di Data Warehousing: le novità
Sviluppo con l’ADO.NET Entity Framework
Conclusioni
Installazione
La splash screen di installazione (Figura 1) ci propone subito le usuali scelte quali i requisiti hardware/software o la visualizzazione delle note di rilascio. La versione a 32 bit richiede un processore Pentium con velocità minima di 600 MHz, anche se è consigliato 1 GHz o superiore, e 512 MB di RAM (anche qui, è meglio orientarsi da subito su 1 GB). Può essere installata su Windows XP Professional dotato di Service Pack 1, su Windows Server 2003 con Service Pack 1 o versione R2, e sulle edizioni Business, Enterprise ed Ultimate di Windows Vista. Inoltre, pressoché tutti gli strumenti di amministrazione e sviluppo richiedono Internet Explorer 6 o superiore. Se vogliamo usufruire delle capacità di reportistica dei Reporting Services sarà necessario avere a disposizione anche IIS 5.0 o superiore, oltre al .NET Framework 2.0 (ASP.NET 2.0 sarà automaticamente abilitato in IIS dal setup dei Reporting Services). Si tenga presente che il supporto nativo per i Web Services è disponibile solo su Windows Server 2003.
Figura 1: la splash screen di installazione di SQL Server 2008 Developer Edition.
Per la CTP di giugno 2007 non era ancora disponibile il Best Practices Analyzer; in compenso, se si vuole aggiornare una versione precedente di SQL Server, è già disponibile l’Upgrade Advisor. Procediamo allora con l’installazione, che per prima cosa provvederà a configurare il client nativo ed i file di supporto all’installazione. L’audit della configurazione produrrà il report così familiare agli utenti di SQL Server 2005, che potremo visualizzare, salvare su file o inviare per e-mail. Per quanto siano cambiati i singoli componenti, i servizi installabili sono gli stessi di SQL Server 2005 (Database, Analysis, Reporting, Integration e tool di sviluppo). Si noti che l’installazione di default non comprende i database di esempio. Anche i passi successivi dell’installazione non sono affatto variati, con la scelta di una Default o di una Named Instance, degli account per ogni servizio (di default viene proposto un utente di dominio per tutti i servizi), del tipo di autenticazione e dei servizi da avviare automaticamente. Per portare a buon fine l’installazione sia dei Reporting Services che di Management Studio, dallo snap-in di IIS 7.0 dovremo assegnare gli opportuni permessi agli utenti dei servizi di SQL Server 2008.
Policy-based management
Dimenticate l’amministrazione tramite script: in un mondo di regulatory compliance, le modalità d’utilizzo del database server saranno stabilite dal DBA mediante policy, le cui regole sono raccolte nel Declarative Management Framework . Le informazioni su questa piattaforma sono raccolte nella voce Administering Servers by Using Declarative Management Framework dei BOL In Figura 2 vediamo una schermata del nuovo SQL Server Management Studio, con evidenziata l’area Management ove troviamo la nuova voce Policy Management.
Figura 2: il nuovo SQL Server Management Studio, con evidenziata la nuova area relativa alla gestione del database server.
Il Declarative Management Framework (DMF) permette di gestire una o più istanze di SQL Server 2008, a livello del singolo oggetto. Dopo aver creato le proprie policy, il DBA può scegliere se applicarle manualmente, in maniera esplicita, o in maniera automatica, in una delle tre possibili modalità di esecuzione: Enforce, Check on Changes o Check on Schedule. La prima fa uso di trigger che prevengono ogni violazione delle policy, la seconda invece solleva eventi (e richiede, dunque, l’abilitazione dei Notification Services) al verificarsi di determinate condizioni; l’ultima modalità usa un job di SQL Server Agent per verificare periodicamente il livello di compliance.
Il DMF distingue fra target, ovvero insiemi di oggetti della (o delle) istanze di SQL Server cui le policy vengono applicate, e facet, insiemi di proprietà che modellano le caratteristiche applicate ad un target; un’espressione booleana nota come condition descrive quali delle regole di una facet si applicano ad un target. Definiti a livello amministrativo target e facet, una policy è l’insieme di una condition e delle condizioni al contorno che descrivono il comportamento atteso, ad es. la modalità di esecuzione o lo scheduling.
Vediamo praticamente questa struttura. Espandendo la voce Policy management in SQL Server Management Studio, troviamo tre rami relativi proprio a Policies, Conditions e Facets. Quelle mostrate in Figura 3 sono le facet comprese nell’installazione standard di SQL Server 2008, ed è facile vedere come esse coprano praticamente ogni oggetto disponibile in SQL Server. Gli altri due rami, all’inizio, sono vuoti. Se andiamo a visualizzare il contenuto di una facet con un doppio click sulla stessa troveremo tutte le proprietà applicabili all’oggetto cui la facet fa riferimento (Figura 4); all’inizio le voci relative alle policy e condizioni dipendenti sono, naturalmente, vuote.
Figura 3: i tre rami di Policy Management relativi a policy, condizioni e facet.
Figura 4: le proprietà pertinenti alla facet database.
Una policy può appartenere ad uno ed un solo policy group, che è un raggruppamento di tipo amministrativo; un database può essere soggetto a più policy group, e sempre al default policy group. E’ opportuno che la modalità di esecuzione Check on Schedule è sempre applicabile, laddove Check on Changes richiede che i cambiamenti alle proprietà di una facet possano sollevare eventi, ed Enforce necessita del supporto transazionale per l’esecuzione delle istruzioni DDL.
Abbiamo detto, poco sopra, che all’inizio le voci di una facet relative a policy e condizioni dipendenti sono vuote: da menu contestuale possiamo scegliere di crearne di nuove (Figura 5).
Figura 5: da menu contestuale possiamo legare una
nuova policy, o condition, ad una facet.
La facet Surface Area, mostrate in Figura 6, sono di peculiare interesse ai fini della sicurezza dell’istanza di SQL Server: questa facet, il cui target è l’intero server, rispecchia fedelmente il contenuto della voce Configuration for Features del quasi omonimo tool Surface Area Configuration.
Figura 6: le proprietà della facet Surface Area.
Supponiamo di voler creare una regola che prevenga, per tutti i database del server, l’uso delle extended stored procedure per usufruire delle funzionalità di OLE Automation e delle stored procedure del Web Assistant, che nelle versioni precedenti permettevano di creare, tramite wizard, file HTML a partire dai contenuti dei database, ma sono oggi deprecate in favore delle funzionalità XML incluse nel DBMS.
Nella schermata di Figura 5, scegliamo New Condition…; nella finestra che comparirà, ci basta dare un nome alla regola e dare il valore False alle proprietà relative alle due funzionalità che vogliamo disabilitare, usufruendo di semplici finestre a discesa per la proprietà, l’operatore da applicare ed il valore da testare, e specificando AND nell’ovvia casella AndOr per applicare contemporaneamente le due regole (figura 7)
Figura 7: ci bastano delle semplici scelte da caselle a discesa per imporre la disabilitazione di alcuni servizi per l'intero database server.
La finestra di lavoro per la creazione di una nuova policy è un po’ più ricca (Figura 8), ma riassume semplicemente quanto finora detto. Occorre dare un nome alla policy, specificare se è attiva o meno, e scegliere la condition alla base della nuova policy; si notino i due pulsanti Edit e New che aprono la dialog di Figura 7 per modificare una condition esistente, o crearne una nuova.
A questo punto, occorre scegliere il gruppo cui appartiene la policy e, nella finestra centrale, i target ai quali la policy è applicabile. Se, per esempio, avessimo organizzato la nostra farm SQL Server in maniera da avere dei restricted e dei public server, avremmo potuto applicare un filtro per rendere la policy attiva sui soli restricted. In basso vediamo le possibili modalità di esecuzione; congruentemente con quanto prima affermato, non è possibile scegliere la modalità Enforce, giacchè non è certo possibile verificare tramite trigger se dei servizi sono attivi. In alto vediamo un messaggio d’errore giacchè non avevo ancora definito uno scheduling per la policy: cliccando su New si aprirà l’usuale job scheduler.
Figura 8: la creazione di una nuova policy.
A questo punto, dal menu contestuale del server in Management Studio, scegliamo Policies e poi Run now (Figura 9). Ogni oggetto monitorabile tramite una policy in SQL Server 2008 presenta la medesima voce nel menu contestuale; View e Facets presentano una comoda visualizzazione a griglia rispettivamente delle policy e delle facet pertinenti all’oggetto, e nella dialog associata a Facets è presente anche un comodo pulsante New Policy from Facet. Vi è una quarta voce, relativa alle sottoscrizioni di gruppo, per gli oggetti ai quali queste ultime sono applicabili.
Figura 9: ogni oggetto monitorabile tramite una policy
presenta una voce di menu contestuale Policies.
Nella dialog che si aprirà (Figura 10), selezioniamo la policy da testare e clicchiamo sul pulsante Check; possiamo poi chiedere il dettaglio del report. Se dalla Surface Area Configuration abilitiamo il Web Assistant, una successiva esecuzione ci mostrerà la non-compliance (Figura 11); la potenza del DMF si palesa dal fatto che, cliccando sul pulsante Configure, verrà immediatamente sanata la non-conformità: se andiamo a riaprire la Surface Area Configuration, vedremo che il Web Assistant è stato abilitato. Di passaggio, farà piacere sapere che in Katmai non è più necessario riavviare il servizio SQL Server per applicare i cambiamenti apportati tramite i tool di configurazione.
Rispetto al modus operandi illustrato in quattro passi (facet – condition – policy – check), quello che cambia sono le possibilità offerte dalla dialog di creazione delle condition e la modalità di esecuzione delle policy; la Figura 12 ci mostra una regola di naming per le tabelle di un database, applicata al database stesso tramite una sottoscrizione di gruppo. L’attivazione della regola avviene tramite la modalità Enforce, al momento della creazione di una nuova tabella.
Figura 10: il test di una policy: la nostra istanza di SQL Server soddisfa le condizioni espresse in Figura 7
Figura 11: una situazione di non-compliance alla policy dei servizi.
Figura 12: una regola di naming per le tabelle di un database, applicata tramite una sottoscrizione di gruppo e verificata all'atto della creazione di una nuova tabella, in modalità Enforce.
Si noti che alle policy di SQL Server 2008 è associato un usuale file di log (Figura 13). Le policy sono fisicamente memorizzate come un gruppo di tabelle nel database di sistema msdb (Figura 14); si tenga presente, altresì, che le policy possono essere esportate/importate in formato XML da menu contestuale, tramite la voce Export policy. Per l’amministrazione del DMF, nello stesso database msdb è stato creato il nuovo ruolo PolicyAdministratorRole (Figura 15)
Figura 13: il logging di SQL Server 2008 incorpora già quello delle policy.
Figura 14: le policy sono immagazzinate come
un insieme di tabelle nel database di sistema msdb.
Figura 15: per l'amministrazione del Declarative Management Framework è stato creato il nuovo ruolo PolicyAdministratorRole.
E’ necessario tenere a mente, infine, un semplice insieme di regole di sicurezza:
-
Un sysadmin o un dbo possono sottoscrivere un database ad una policy, o gruppo di policy
-
I membri del ruolo PolicyAdministratorRole possono abilitare o disabilitare policy.
-
I membri del ruolo PolicyAdministratorRole possono creare policy che essi stessi non possono eseguire su richiesta (ad hoc).
-
L’esecuzione ad hoc delle policy avviene nel contesto di sicurezza dell’utente.
-
Le policy in modalità di esecuzione Check on Schedule fanno uso di job del SQL Server Agent, il cui proprietario è sa.
La piattaforma di Data Warehousing: le novità
Per quanto sia trasversale all’intera piattaforma e non direttamente legato alle necessità di Data Warehousing, il supporto alla compressione dati in SQL Server 2008 è stato introdotto pensando soprattutto allo storage delle fact tables. Incidentalmente, SQL Server 2008 supporta ora in modalità nativa la compressione hardware per i dispositivi di backup; se si vuole incrementare la velocità di backup/restore, a discapito dello spazio occupato, si può utilizzare il trace flag 3205: si tenga però presente che i trace flag sono una feature passibile di rimozione nelle versioni successive. Nella stessa direzione vanno il parallelismo delle tabelle partizionate e l’ottimizzazione delle query star join, dettagliatamente illustrata quest’ultima dalla voce Optimizing Data Warehouse Query Performance Through Bitmap Filtering dei BOL.
E’ di peculiare interesse per il monitoraggio dei cambiamenti, soprattutto nelle operazioni di ETL (Extraction, Transformation and Loading), invece, la tecnologia di Change Data Capture (CDC). Le operazioni di insert, update e delete su una tabella monitorata (purchè non appartenga ad un database di sistema o di distribuzione) vengono immagazzinate in formato relazionale, insieme con i metadati relativi ai cambiamenti apportati. La CDC viene abilitata a livello dell’intero database da un membro del gruppo sysadmin; un db_owner potrà poi configurarla a livello delle singole tabelle. L’abilitazione a livello dell’intero database avviene con la chiamata ad una stored procedure nel contesto del database:
USE Ordini;
GO
EXECUTE sys.sp_cdc_enable_db_change_data_capture;
GO
L’esecuzione di questa stored procedure crea lo schema e l’utente cdc, ed una serie di tabelle (Figura 16). La struttura di queste tabelle, che ospitano anche i metadati, è riportata nei BOL alla voce Configuring Change Data Capture.
Figura 16: la funzionalità di Change Data Capture
crea un insieme di tabelle, uno schema ed un utente.
Un’analoga stored procedure sp_cdc_enable_table_change_data_capture permette poi di abilitare il CDC per le singole tabelle; questa sp richiede che sia attivo il SQL Server Agent e, com’è facile immaginare, ha una serie di parametri:
@source_schema sysname,
@source_name sysname,
@capture_instance sysname = null,
@supports_net_changes bit = 0,
@role_name sysname,
@index_name sysname = null,
@captured_column_list nvarchar(max) = null,
@filegroup_name sysname = null
tra i quali è obbligatorio specificare almeno lo schema, la tabella da monitorare e l’utente nel cui contesto avverrà la cattura dei dati:
EXECUTE sys.sp_cdc_enable_table_change_data_capture
@source_schema = N'dbo'
,@source_name = N'RV_Clienti'
,@role_name = N'cdcAdmin'
GO
Il significato degli altri parametri è dettagliato nei BOL; è intuitivo, comunque, che si va a specificare le colonne da monitorare, il filegroup che contiene la tabella dei cambiamenti (se diverso da quello di default) e l’istanza di cattura dati. Gli altri due parametri richiedono qualche spiegazione aggiuntiva. L’esecuzione della stored procedure crea una table-valued function, il cui nome è ottenuto concatenando cdc.fn_cdc_get_all_changes con l’identificativo della tabella, e che viene utilizzata per recuperare tutti i cambiamenti effettuati in un intervallo di tempo, come vedremo in seguito. Se il parametro @supports_net_changes viene impostato ad 1, verrà creata una seconda table-valued function, il cui nome comincia con cdc.fn_cdc_get_net_changes, che riporta un raggruppamento dei cambiamenti apportati ad ogni riga distinta nell’intervallo. Per questo tipo di cattura, è necessario che la tabella da monitorare abbia una chiave primaria o un indice univoco; in quest’ultimo caso, è necessario riportare l’indice come valore del parametro @index_name.
L’esecuzione della stored procedure sys.sp_cdc_enable_table_change_data_capture darà inoltre luogo a due job di SQL Server Agent, cdc.Ordini_capture e cdc.Ordini_cleanup, che pianificano l’esecuzione dei rispettivi agenti. I cambiamenti apportati alle tabelle monitorate vengono ordinati temporalmente in base al commit log sequenze number (LSN); la tabella cdc.change_tables ha un’apposita colonna, start_lsn, che memorizza l’LSN da cui parte la memorizzazione dei dati. Periodicamente, il job di cleanup si occupa di cancellare i dati vecchi, e di aggiornare il valore di start_lsn. Gli LSN di inizio e fine cattura vengono passati alla stored procedure fn_cdc_get_all_changes per visualizzare i cambiamenti apportati nell’intervallo specificato:
DECLARE @from_lsn binary(10), @to_lsn binary(10);
SET @from_lsn = sys.fn_cdc_get_min_lsn('dbo_RV_Clienti');
SET @to_lsn = sys.fn_cdc_get_max_lsn();
SELECT * FROM cdc.fn_cdc_get_all_changes_dbo_RV_Clienti(@from_lsn, @to_lsn, 'all');
Nel nostro caso, il risultato è il seguente:
0x0000001B000000AE0014,0x0000001B000000AE0013,2,0x0F,1,Mario,Rossi,Piazza Nazionale 31
avendo operato una
INSERT INTO RV_Clienti(Nome, Cognome, Indirizzo) VALUES ('Mario', 'Rossi', 'Piazza Nazionale 31')
I BOL riportano, altresì, come effettuare un’interrogazione per data, piuttosto che come monitorare i cambiamenti alla struttura stessa di una tabella tramite la stored procedure sys.sp_cdc_get_ddl_history.
L’introduzione del nuovo statement SQL MERGE va nella direzione di facilitare alcuni scenari comuni nel campo dell’ETL, quali verificare se una riga esiste ed effettuarne quindi l’inserimento/aggiornamento/cancellazione, il tutto con un’unica istruzione. I BOL riportano un esempio in cui l’inventario dei prodotti di AdventureWorks viene aggiornato, su base giornaliera, a partire dai dati nella tabella degli ordini, modificando la quantità disponibile e cancellando i prodotti la cui quantità scende a zero:
MERGE Production.ProductInventory AS pi
USING (SELECT ProductID, SUM(OrderQty) FROM Sales.SalesOrderDetail sod
JOIN Sales.SalesOrderHeader soh
ON sod.SalesOrderID = soh.SalesOrderID
AND soh.OrderDate = GETDATE()
GROUP BY ProductID) AS src (ProductID, OrderQty)
ON (pi.ProductID = src.ProductID)
WHEN MATCHED AND pi.Quantity - src.OrderQty <> 0
THEN UPDATE SET pi.Quantity = pi.Quantity - src.OrderQty
WHEN MATCHED AND pi.Quantity - src.OrderQty = 0
THEN DELETE;
Tramite GROUPING SETS, un’estensione alla clausola GROUP BY, possiamo ora creare raggruppamenti multipli nella stessa query; il resultset derivante è equivalente ad una UNION ALL, ROLLUP o CUBE di righe raggruppate in maniera differente. Ad esempio, l’istruzione
SELECT customer, year, SUM(sales)
FROM T
GROUP BY GROUPING SETS ((customer), (year))
è equivalente a
SELECT customer, NULL as year, SUM(sales)
FROM T
GROUP BY customer
UNION ALL
SELECT NULL as customer, year, SUM(sales)
FROM T
GROUP BY year
Due novità vanno, infine, nella direzione di una maggiore scalabilità degli Integration Services, che possono comunque essere installati side-by-side con quelli di SQL Server 2005, e supportano ancora i DTS di SQL Server 2000: i miglioramenti nella pipeline, e nelle operazioni di lookup.
Sviluppo con l’ADO.NET Entity Framework
Grazie all’ADO.NET Entity Framework gli sviluppatori potranno usare SQL Server 2008 per scrivere la logica applicativa in termini di entità di business, piuttosto che usare tabelle e righe; un modello concettuale Entità – Relazioni può così essere tradotto immediatamente in codice, eliminando l’impedance mismatch fra linguaggi di programmazione O-O e l’uso di SQL per l’accesso e la manipolazione dei dati: un’operazione nota come ORM (Object-Relational Mapping). L’accesso ai dati direttamente dal linguaggio di programmazione si basa su LINQ, una delle principali novità di C# 3.0/VB.NET 9.0 e del .NET Framework 3.5 alla base di Visual Studio 2008.
LINQ abilita l’esecuzione di query orientate agli insiemi e fortemente tipizzate sia versus lo stack ADO.NET connected (System.Data.Sqlclient) che verso i DataSet di ADO.NET e il nuovo Entity Data Service Mapping provider. In termini di middleware, (http://www.devx.com/dotnet/Article/33328, http://msdn2.microsoft.com/en-us/library/aa697427(VS.80).aspx#ado.netenfrmovw_topic3) permettono la materializzazione, il tracking delle modifiche e la persistenza dei dati come oggetti del CLR.
L’ADO.NET Entity Framework sarà rilasciato nella prima metà del 2008 e comprenderà LINQ to Entities , un componente di accesso ai dati, implementato come insieme di estensioni ai linguaggi .NET, che permetterà di effettuare query direttamente sull’Entity Data Model (http://www.microsoft.com/downloads/details.aspx?FamilyID=BA67E85E-38F6-4943-8731-B8618472E899&displaylang=en, http://www.devx.com/dotnet/Article/32964/0/page/3, http://blogs.msdn.com/adonet/archive/2007/05/30/entitysql.aspx) di una sorgente dati.
Per tutte le tecnologie citate consiglio, in questa fase, di fare riferimento all’ADO.NET team blog. Vediamo ora un esempio concreto delle possibilità offerte dall’ADO.NET Entity Framework, tratto dalla documentazione:
// we'll use the order-tracking store
using(OrderTracking orderTracking = new OrderTracking()) {
// find all the pending orders for sales people
// in Washington
var orders = from order in orderTracking.SalesOrders
where order.Status == "Pending Stock Verification" &&
order.SalesPerson.State == "WA"
select order;
foreach(SalesOrder order in orders) {
// obtain a list of StockAppProduct objects
// to be used for validation
List<StockAppProduct> products = new List<StockAppProduct>(
from orderLine in order.Lines
select new StockAppProduct {
ProductID = orderLine.Product.ID,
LocatorCode = ComputeLocatorCode(orderLine.Product)
}
);
Siamo riusciti ad ottenere la lista di tutti i prodotti per i quali è necessaria una verifica a magazzino, ordinati dal personale di vendita dello stato di Washington. Non solo abbiamo un codice perfettamente rispondente alla logica del problema (si noti come siamo ricorsi ad un modello dei dati che astrae dalla conoscenza aggiuntiva che la normalizzazione della base dati potrebbe richiedere), ma siamo anche riusciti a scrivere uno snippet data-intensive, senza dover fare ricorso al codice infrastrutturale solitamente necessario per scrivere un’applicazione da database: oggetti per la gestione della connessione, linguaggi esterni come SQL per formulare le interrogazioni, configurazioni, valorizzazione dei parametri, costrutti per la trasformazione e l’iterazione dei risultati.
Per raggiungere questo obiettivo, ADO.NET introduce l’Entity Framework, che consiste di un modello dei dati e di un insieme di servizi a tempo di progettazione e di esecuzione che permettono allo sviluppatore di descrivere i dati dell’applicazione ed interagire con essi al livello di astrazione concettuale, isolando l’applicazione dallo schema logico del database sottostante. Allo scopo, è stato necessario creare un modello dei dati E-R, che potesse descriverli utilizzando costrutti di alto livello, quello concettuale per l’appunto: il già citato Entity Data Model (EDM). Oltre ai concetti base di entità e relazione (e loro insiemi), l’EDM introduce concetti tipici del mondo O-O quali l’ereditarietà fra le entità (meramente strutturale, ovvero è possibile ereditare la struttura dei dati, ma non i behavior) e l’introduzione di tipi complessi, assai simili alle classi, accanto a quelli semplici (int, varchar, datetime…) solitamente supportati da un DBMS. Non manca qualche eredità da UML, soprattutto nella tassonomia delle relazioni. Di passaggio, si noti che le specifiche EDM 1.0 supportano le sole relazioni binarie, viste sempre come bidirezionali.
Uno schema EDM viene descritto da un file XML che descrive lo schema concettuale usando lo Schema Definition Language (SDL); com’è facile immaginare, Visual Studio 2008 offre un tool visuale per la progettazione di questi schemi: dal solito Add | New Item, scegliamo la voce ADO.NET Entity Data Model (che genererà un file con estensione .csdl), diamogli un nome significativo, e partirà un wizard (Figura 17) che permette di creare un modello direttamente da un database esistente. Il wizard richiede di specificare direttamente un file .mdf e, alla data, opera col solo client per SQL Server incluso nel .NET Framework (Figura 18). La Beta 1 necessita ad oggi di una patch o il wizard genererà dei modelli vuoti. E’ facile riconoscere nella Toolbox del designer (Figura 19) gli elementi per i concetti O-O dell’EDM testè ricordati.
Figura 17: con Visual Studio 2008 possiamo creare, in maniera visuale, un modello concettuale dei dati, direttamente dal database.
Figura 18: il wizard per la creazione di un Entity Data Model richiede di specificare direttamente un file MDF ed usa System.Data.Sqlclient.
Figura 19: il designer grafico di modelli EDM per Visual Studio.
Il medesimo tool genererà un secondo file XML, con estensione .cs.msl, che descrive il mapping fra lo schema concettuale descritto dall’EDM e quello logico del DBMS sottostante, permettendo l’aggiornamento a due vie ed in particolare la persistenza. A questo punto, un provider di accesso ai dati di ADO.NET, il mapping provider, usa l’EDM per fornire all’applicazione una vista concettuale dei dati; il meccanismo è analogo a quello con cui un normale provider, ad es. quello nativo per SQL Server, fornisce all’applicazione una vista dello schema (logico) di un database. In effetti, il mapping provider usa internamente sia i file XML che descrivono lo schema concettuale ed il mapping fra questo e quello logico, che un tradizionale provider (come System.Data.SqlClient) per l’accesso al DBMS.
Quello che manca nel mosaico è, ora, un linguaggio di query che operi direttamente sul modello Entità – Relazioni descritto dall’EDM. L’Entity Framework comprende tale linguaggio, noto come Entity SQL o eSQL: sintatticamente molto simile ad SQL, richiede esplicitamente gli alias per le entità (che prendono il posto delle tabelle); poiché EDM consente di modellare le relazioni, scompare l’operatore di join ed al suo posto abbiamo un operatore di NAVIGATE fra le relazioni; e supporta l’ereditarietà fra entità tramite l’operatore IS OF. Avendo definito, nel modello concettuale, un’entità “prodotto non più in vendita” (che a tutti gli effetti diventa una classe del sistema dei tipi di .NET), si immagini la comodità di poter scrivere una query del genere:
SELECT VALUE p
FROM AdventureWorks.Products AS p
WHERE p IS OF (AdventureWorks.DiscontinuedProduct)
o, volendo trovare tutti i venditori che hanno piazzato ordini superiori a 200.000 dollari:
SELECT VALUE sp
FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp
WHERE EXISTS(
SELECT VALUE o
FROM NAVIGATE(p, AdventureWorks.SalesPerson_Order) AS o
WHERE o.TotalDue > 200000)
Per dirla in parole più semplici, e familiari allo sviluppatore: siamo in grado di creare un modello dei dati che incorpori la logica di business, senza dover scrivere un middleware applicativo che descriva la logica di business in termini della vista relazionale dei dati. Ribadisco ancora una volta: grazie all’Object Services layer incluso nell’ADO.NET Entity Framework, le entità di business espresse nel modello EDM vengono viste come classi .NET, e le loro interazioni in termini di codice. Più precisamente, per ogni entità del modello EDM viene generata una partial class che, in quanto tale, può essere estesa con logiche di business custom, definite in file separati, senza interferire col generatore di codice. La classe ha un contratto ben definito, espresso proprio dalla sua rappresentazione come entità nell’EDM.
L’aver creato uno strato intermedio permette, non solo di non effettuare alcuna modifica al sistema dei tipi di .NET ed alla loro gestione, ma anche di poter mantenere un atteggiamento agnostico: il codice può scegliere se consumare i dati del database in termini di righe e colonne, o di oggetti. Il meccanismo dei generics del .NET Framework 2.0 e successivi, insieme con la risoluzione dei tipi a run-time e con il meccanismo di ereditarietà fra entità introdotto dall’EDM, ci consentono ora di scrivere un codice estremamente compatto e leggibile per processare tutti gli ordini inevasi, sulla base della loro natura:
using(AdventureWorksDB aw = new AdventureWorksDB(Settings.Default.AdventureWorks)) {
Query<SalesOrder> pendingOrders = aw.GetQuery<SalesOrder>(
"SELECT VALUE " +
"FROM AdventureWorks.AdventureWorksDB.SalesOrders AS o " +
"WHERE o.Status = 0");
foreach(SalesOrder o in pendingOrders) {
// usa il tipo a runtime per decidere come processare l’ordine
if(o is StoreSalesOrder) {
ValidateTaxByState((StoreSalesOrder)o);
ProcessLocalOrder(o);
}
else {
ProcessOnlineOrder(o);
}
}
}
Questo esempio racchiude un’ultima necessità di conoscenza del modello relazionale, ovvero dobbiamo sapere che la colonna (trasformata in proprietà) Status assume valore pari a 0 per gli ordini inevasi, ma avremmo potuto, a necessità, definire un’entità PendingSalesOrders, che mappasse tutti i record del DBMS sottostante aventi Status=0. Laddove la potenza dell’ADO.NET Entity Framework si dispiega nella sua interezza è però nel fatto che, a seguito di inserimenti, modifiche o cancellazioni sui dati applicate nel modello ad oggetti, esso ha abbastanza metadati a disposizione per poter persistere i cambiamenti effettuati, tramite una semplice chiamata ad un metodo SaveChanges(). La persistenza avviene utilizzando i lock di concorrenza ottimistica.
Per quanto la sintassi eSQL sopra utilizzata sia estremamente più naturale e vicina al linguaggio del problema dell’usuale SQL, ci troviamo ancora di fronte ad un letterale stringa incorporato nel codice: non solo abbiamo ancora, a tutti gli effetti, un secondo linguaggio rispetto a quello usato per scrivere il nostro codice ma, soprattutto, il compilatore non può validare la sintassi utilizzata, o generare un warning nel caso in cui entità o relazioni non esistano nel database. E’ qui che entra in gioco il progetto LINQ, un set di estensioni a C#/VB.NET che permette di esprimere le query ad una sorgente dati (che può essere l’EDM, così come un database relazionale, un DataSet, un file XML o una struttura dati in memoria) direttamente in uno di questi due linguaggi.
Ecco allora che la query per trovare tutti gli impiegati assunti dopo una certa data, che facendo uso degli Object Services e di eSQL scriveremmo così:
Query<SalesPerson> newSalesPeople = aw.GetQuery<SalesPerson>(
"SELECT VALUE sp " +
"FROM AdventureWorks.AdventureWorksDB.SalesPeople AS sp " +
"WHERE sp.HireDate > @date",
new QueryParameter("@date", hireDate));
utilizzando LINQ diventa:
var newSalesPeople = from p in aw.SalesPeople
where p.HireDate > hireDate
select p;
La possibilità di effettuare query, non solo direttamente sul modello concettuale, ma anche in termini del linguaggio di programmazione usato, è nota come LINQ to Entities e, come accennato, sarà inclusa nella versione definitiva dell’ADO.NET Entity Framework. Il compilatore è ora in grado di verificare, non solo la sintassi, ma anche l’esistenza delle entità e la compatibilità fra i loro tipi. LINQ offre un sistema di query che comprende tutti gli operatori noti dell’algebra relazionale: proiezione, join, ordinamento, raggruppamento, e così via. Esistono altre “varianti” di LINQ specificamente pensate per le interrogazioni a DataSet, file XML e database relazionali, ma per non appesantire il discorso, rimando senz’altro alla numerosa documentazione disponibile sul sito citato.
Conclusioni
SQL Server 2005 ha rappresentato un deciso cambio di rotta rispetto alle precedenti versioni del DBMS e molte delle novità di SQL Server 2008 andranno nella direzione di un miglioramento e dell’estensione di funzionalità esistenti; non mancheranno, tuttavia, le nuove funzionalità. Ho presentato le principali già disponibili nella CTP di giugno 2007, ovvero la gestione tramite policy ed il Declarative Management Framework (DMF), i miglioramenti in termini di data warehousing ed in particolare la Change Data Capture, e l’ORM (Object-Relational Mapping) offerto dall’ADO.NET Entity Framework in congiunzione con LINQ. La prossima CTP presenterà un’interessante novità, il Resource Governor, con funzionalità di monitoraggio, prioritizzazione ed imposizione di limiti di risorse ai carichi di lavoro. Vedremo inoltre all’opera le funzionalità di geolocalizzazione (location awareness) e di gestione dei dati destrutturati. Varie novità sono di ordine minore in termini di feature interessate, ma non per questo meno utili, quali la possibilità di aggiungere CPU a caldo al database server o di effettuare ricerche nei dati criptati.