Introduzione ad ADO.NET Stefano Del Furia Lab Informatica ISIS “Enrico Fermi” Bibbiena (AR) Mail: [email protected] Web: http://www.edudotnet.it Il passato… ODBC (Open Database Connectivity) tentativo di creare uno standard per l’accesso ai dati orientato alla gestione di dati in forma tabellare sistema per accedere a dati provenienti da svariate piattaforme molto veloce, supporta molte delle istruzioni del linguaggio SQL si devono scrivere “svariate” righe di codice solo per stabilire una semplice connessione al database, figurarsi per realizzare una applicazione completa DAO (Data Access Objects) accedere in modo nativo al database Access (Jet-Engine) il modello ad oggetti è considerato da tutti orribile, lento ormai non quasi più usato da nessuno RDO (Remote Data Objects) evoluzione di ODBC e DAO considerato migliore di DAO, in quanto più veloce, e con il modello ad oggetti più raffinato più facile da usare di ODBC Il passato… OLE DB successore di ODBC e tentativo, (riuscito), di avere uno standard comunicare con i database che non fossero solo in forma tabellare utilizza un provider (simile ad un driver di ODBC) molto veloce nell’accesso ai dati l’unico inconveniente che non è possibile svilupparci delle applicazioni in tutti i linguaggi (per esempio Visual Basic 6.0 è escluso) ADO (ActiveX Data Objects) doveva essere il nuovo RDO. è basato su OLE DB, il che fornisce un elevato livello di flessibilità 22 Modelli di accesso ai dati molte funzionalità come Cursori, Filtri da la possibilità di eseguire ordinamenti lato clienti ADO è più lento di RDO Il presente: ADO.NET ADO .NET insieme di classi che forniscono tutte le funzionalità necessarie per l’accesso ai dati di un database e per la loro elaborazione in memoria. distingue concettualmente tra l’accesso ai dati del database e la loro elaborazione. il modello ad oggetti fornito è suddiviso in due parti, oggetti “connessi” ed oggetti “disconnessi”, due tipologie di oggetti sono completamente distinte Oggetti connessi vengono eseguite le operazioni di lettura e aggiornamento sul database progettati per interfacciarsi con uno specifico DBMS appartengono ad un determinato .NET Provider Oggetti disconnessi consentono la memorizzazione e l’elaborazione dei dati nella memoria del programma client completamente indipendenti dal database non sono mai in comunicazione con esso Modalità di accesso ai dati Modalità di accesso ai dati Connesso: Forward-only, read-only L’applicazione esegue una query, ottiene I risultati e li processa “immediatamente” Cursore “Firehose”, altissime prestazioni e bassissimo impatto in termini di risorse Oggetto DataReader Disconnesso L’applicazione esegue una query, recuperà I dati e li memorizza localmente per l’elaborazione Minimizza il tempo di connessione al database DataSet object Acceso Connesso ai Dati Nel modello client-server ogni cliente crea la propria connessione al DB all’avvio… …e la rilascia quando il programma termina. Il server deve mantenere una connessione attiva per ogni client… …anche se la connessione è utilizzata per una piccola frazione di tempo. idle Il modello connesso consuma risorse anche quando non sarebbe necessario busy idle Clients Connections Server Acceso Connesso ai Dati Il DB server fornisce un “cursore” per mantenere il concetto di riga corrente Il client può leggere ( o scrivere) nella riga corrente mediante il cursore Ci si può spostare con il “commando” MoveNext Per supportare applicazioni GUI qualche database gestisce anche cursori ‘scrollabili’ (es. MovePrevious) Client rs Server Cursor Query Results Tables Acceso Connesso ai Dati Vantaggi Si crea la connessione solo una volta Si impostano le proprietà della 'sessione‘ mediante la connessione Possibilità di effettuare lock e gestire la sicurezza se è previsto dal DB La prima riga di ogni query è 'immediatamente‘ disponibile Svantaggi Le connessioni che tengono “aperto” il database consumano risorse non molto scalabile Non adatto ad app Web utente non si “scollega” il numero di utenti non è prevedibile Non adatto ad app n-tier Le connessioni non possono essere passate tra i livelli Accesso Disconnesso ai Dati Nel modello disconnesso, le connessioni al DB sono rilasciate appena possibile I Dati possono essere utilizzati anche dopo che la connessione è stata chiusa La connessione viene ri-creata solo per scrivere delle modifiche nel server idle Il modello disconnesso utilizza le risorse solo quando è necessario busy idle Clients Connections Server Accesso Disconnesso ai Dati Tutti I dati sono inviati al client in un unica operazione Il risultato delle query viene mantenuto in memoria Tutte le risorse del server vengono rilasciate Il client manipola ed aggiorna i dati off-line Il client si ri-connette al database per scrivere gli aggiornamenti Utilizzo della chiave primaria per iidentificare il record corretto Client rs Cursor Server Query Results Accesso Disconnesso ai Dati Vantaggi Un singolo DB server può gestire più utenti La manipolazione dei dati è più veloce e flessibile I dati non sono “vincolati” alla connessione facilità di passaggio tra I vari livelli o per scriverli su file Altamente indicato per app Web e n-tier Svantaggi Aprire e chiudere la connessione può essere “costoso” Recuperare molte righe può essere molto lento Necessita di potenza lato client Meno opzioni di lock Panoramica su ADO.NET Panoramica su ADO .NET DataSet DataRow Data Provider DataTable DataAdapter DataReader Command Connection Organizzazione di ADO.NET Due grandi aree: .NET Data Providers Un provider per ogni sorgente dati Ogni provider ha il proprio namespace Supporti per la programmazione “connessa” Le Forms (WEB/WIN) fanno tra tramite tra DataSet e data source System.Data namespace Fornisce il modello ad oggetti del DataSet Supporto alla programmazione disconnessa Fornisce un modello di programmazione basato su interfacce per l’accesso “generico” ai managed providers Managed Providers Applicazione ADO.NET Managed Provider OLE DB Provider SQL Server Database SQL Managed Provider Database ADO Managed Provider Modelli di programmazione ADO.NET fornisce il supporto per i due distinti modelli di programmazione Connesso Usa gli oggetti connessi Command e DataReader Il DataReader è di tipo forward only e read-only Può eseguire aggiornamenti mediante l’oggetto Command Disconnesso Utilizza i DataSets I DataAdapters riempiono i DataSets con i dati ed eseguono le modifiche nel server I DataSets sono indipendenti dal Provider I DataSets sono memorizzati e serializzati come XML .NET Data Providers I .NET Data Providers sono utilizzati per stabilire le connessioni con i DB Ecco alcuni Data Providers del .NET SQL Data Provider (System.Data.SqlClient) ODBC Data Provider (System.Data.Odbc) OLEDB Data Provider (System.Data.OleDb) MySql Data Provider (MySql.Data.MySqlClient) SqlConnection MySqlConnection SqlCommand SqlDataReader SqlDataAdapter OdbcConnection MySqlCommand OleDbConnection OdbcCommand MySqlDataReader MySqlDataAdapter OleDbCommand OdbcDataReader OdbcDataAdapter OleDbDataReader OleDbDataAdapter Classi del .NET Data Provider Ogni Data Provider del .NET ha 4 classi Classe Descrizione Connection Stabilisce una connessione ad una specifica sorgente dati Command Esegue un comando verso la sorgente dati ha dei Parameters e può utilizzare una Transaction per una certa Connection DataReader Legge flussi di dati in modalità forward-only, read-only da una sorgente dati DataAdapter Popola un DataSets e risolve gli aggiornamenti con la sorgente dati Per esempio Il provider per SQL Server implementa SqlConnection The OLE DB data provider implements OleDbConnection Riassumendo…. Il namespace System.Data.SqlClient: Classi per MS SQL Server 7 (e superiori) SqlConnection, SqlAdapter, ... Il namespace System.Data.OleDb Classi per il provider OLEDB OleDbConnection, OleDbAdapter, … Accesso connesso Effettuare una Connessione DataSet DataRow Data Provider DataTable DataAdapter DataReader Command Transaction Connection La classe Connection Stabilisce una connessione al data source Simile alla ‘classica’ classe ADO Connection La stringa Connection controlla la connessione Simile alle precedenti stringhe di connessione La sintassi è dipendente dal provider Es: Sql Server “Server=localhost; Database=Pubs; Integrated Security=SSPI” Proprietà e metodi ConnectionString State Open, Closed, Broken, ... Database Il database attualmente aperto Open() Close() BeginTransaction() Returns a Transaction object L’oggetto Transaction Utilizzato per compiere operazioni di commit o rollback in una transazione o per fissare un “savepoint” Proprietà e Metodi Connection L’oggetto Connection che ha iniziato la transazione IsolationLevel ReadCommited, Serializable, ... Commit() Opera un Commit nel data source Rollback() Opera un Rollback del data source Save() Imposta un savepoint durante una transazione Code Example // Crea ed apre un oggetto SqlConnection SqlConnection cn = new SqlConnection(“Server=localhost; Integrated Security=true. . . . "); cn.Open(); // avvia una Transazione per quella connessione SqlTransaction trn = cn.BeginTransaction(); // fa quello che deve fare e… if(Pagamento(daConto, aConto, importo) == true) // ho pagato la bolletta trn.Commit(); else trn.Rollback(); cnn.Close(); Lettura dei dati DataSet DataRow Data Provider DataTable DataAdapter DataReader Command Connection L’oggetto Command Una volta che si è creata una connesione si utilizza l’oggetto Command per eseguire istruzioni SQL o Stored Procedures Proprietà e metodi CommandType Text, StoredProcedure e TableDirect CommandText query, nome di una tabella o di una stored procedure Parameters Collezione di Parametri ExecuteReader() Ritorna a DataReader ExecuteScalar() Ritorna un singolo valore scalare ExecuteNonQuery() Returna il numero di righe interessate Utilizzarlo per operazioni di update e delete Esempio string cnStr =@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\AmiciAGG.mdb;Persist Security Info=False"; OleDbConnection cn = new OleDbConnection(cnStr); OleDbCommand cm = new OleDbCommand(); cm = new OleDbCommand("select nome from ragazzi where città = ?",cn); cm.Parameters.Add("@Raga", citta); cn.Open(); // fai qualcosa cn.Close(); L’oggetto DataReader Si utilizza il DataReader per leggere delle DataRows Cursore Forward only (Firehose) Proprietà e Metodi Item Collection of fields Read() Reads the next DataRow, returns false when no rows are left Accesso mediante indicizzatori, e Metodi [“Cognome”] [0] GetString() GetDecimal() GetBoolean() Lavorare con i DataReaders ADO.NET non fornisce tutte le opzioni di cursore server-side che ci sono in ADO Es. KeySet Cursor, Static Cursor, Dynamic Cursor Il DataReader fornisce solamente un cursore forewardonly, read-only Utilizzarlo come fosse il classico Recordset ADO Non lasciare aperto il DataReaders più del necessario Usato correttamente i DataReaders sono efficienti Eseguire gli aggioramenti usando oggetti Command con il metodo ExecuteNonQuery() Per una gestione flessibile degli aggiornamenti lato client, utilizzare … DataSets e DataAdapters Esempio string cnStr =@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\AmiciAGG.mdb;Persist Security Info=False"; OleDbConnection cn = new OleDbConnection(cnStr); OleDbCommand cm = new OleDbCommand(); cm = new OleDbCommand("select nome from ragazzi where città = ?",cn); cm.Parameters.Add("@LR", citta); cn.Open(); OleDbDataReader dr = cm.ExecuteReader(); while (dr.Read() == true) // dr.Read() torna false se non ci sono // più righe { Console.WriteLine(“Nome = “,dr["Nome"].ToString()); } cn.Close(); Accesso disconnesso Modalità disconnessa DataSet DataRow Data Provider DataTable DataRow DataAdapter DataReader Command UpdateCommand InsertCommand DeleteCommand SelectCommand Connection Esempio SqlDataAdapter adapter = new SqlDataAdapter(); SqlCommand delete = new SqlCommand("DeleteOrder",cnn); delete.CommandType=CommandType.StoredProcedure; delete.Parameters.Add("@OrderID",typeof(Int32)).SourceColumn="OrderID"; adapter.DeleteCommand = delete; SqlCommand insert = new SqlCommand("AddOrder",cnn); insert.CommandType=CommandType.StoredProcedure; insert.Parameters.Add("@OrderID",typeof(Int32)).SourceColumn="OrderID"; insert.Parameters.Add("@CustD",typeof(Int32)).SourceColumn="CustomerID"; insert.Parameters.Add("@Date",typeof(DateTime)).Value = DateTime.Now; adapter.InsertCommand = insert; SqlCommand update = new SqlCommand("UpdateOrder",cnn); update.CommandType=CommandType.StoredProcedure; update.Parameters.Add("@OrderID",typeof(Int32)).SourceColumn="OrderID"; update.Parameters.Add("@CustD",typeof(Int32)).SourceColumn="CustomerID"; adapter.UpdateCommand = update; adapter.Update(ordersTable); Il DataSet Il DataSet fornisce una cache locale dei dati I DataSets sono sempre disconnessi Usare i DataSets quando: Si vuole fornire all’utente elevate capacità di manipolazione Scrolling, sorting, filtering Si vogliono passare i dati tra i vari “livelli” del programma Es. dal middle-tier al client window forms Si vuole che ADO.NET generi le istruzioni SQL per effettuare le modifiche ai dati del database Il DataSet Usato come un recordset disconnesso Può contenere più DataTable Può memorizzare delle DataRelations Usate per relazionare le DataTable Mantiene le informazioni sullo schema Schema puà essere acquisiti a design-time design-time (typed DataSet) oppure a run-time Proprietà Tables Collezione di DataTable Relations Collezione di Relation EnforceConstraints Abilita i vincoli del dataset Metodi GetChanges Ottiene un DataSet con solo le righe cambiate Merge Effettua il Merge di due DataSets GetXml Ottiene I dati dal DataSet in formato XML GetXmlSchema Ottiene lo schema in XML del DataSet Il modello a oggetti del DataSet DataSet DataTableCollection DataRelationCollection DataTable DataColumnCollection DataColumn DataRowCollection DataRow ConstraintCollection Constraint DataRelation Il DataTable Ogni oggetto DataTable rappresenta una tabella dati in un DataSet Creata da un DataAdapter Ai DataTables in un DataSet si accede usando la proprietà Tables del DataSet DataSet DataTable DataColumn DataRow Collezione di DataTables Ogni DataTable ha: Una proprietà Columns Una proprietà Rows Una proprietà TableName Una proprietà PrimaryKey DataTable DataRow DataColumn L’oggetto DataTable Il DataTable contiene delle DataRow Può gestire i vincoli Unique, ForeignKey Proprietà e metodi Columns Collezione di DataColumns Rows Collezione di DataRows NewRow() Crea una nuova riga Rows.Add() Aggiunge una DataRow alla collezione Rows Select() Crea un sottoinsieme della collezione Rows Il DataColumn Ogni oggetto DataColumn descrive ona colonna in un DataTable Gli uggetti DataColumn descrivono lo schema delle tabelle Il DataAdapter può generare le necessarie DataColumns Le proprietà includono: ColumnName DataType AllowDBNull DataSet DataTable DataColumn DataRow DataTable DataRow DataColumn Il DataRow Un oggetto DataRow fornisce l’accesso ad una riga di dati di un DataTable Gli oggetti DataRow di un DataTable contengono tutti i dati nel DataTable DataSet DataTable DataRow Si accede ai valori del DataRow fornendo: Numero della Colonna [0] Nome della Colonna [“Nome”] Oggetto DataColumn DataColumn DataTable DataRow DataColumn Il DataRow Il DataRow contiene i dati Mantiene lo stato della riga Deleted, modified, inserted e detached Memorizza i valori originalie quelli nuovi Proprietà RowState Added, Modified, Detached, Deleted and UnChanged Item (indicizzatore) Collezione di dati DataSets & DataAdapters I DataSets non sono in grado di comunicare direttamente con i database DataSets sono completamente indipendenti dal data provider I DataAdapters forniscono il “ponte” tra i DataSet ed il data source Il DataAdapter Contiene gli oggetti Command per operazioni di Select, Update, Delete ed Insert sui dati Gli oggetti Update, Delete ed Insert Command possono essere generati “automaticamente” dal CommandBuilder Proprietà e Metodi SelectCommand UpdateCommand DeleteCommand UpdateCommand Fill() Esegue il SelectCommand e memorizza I dati in un DataTable Update() Esegue il comando appropriato per aggiornare il Database Comandi del DataAdapter Ci sono tre modi per creare gli oggetti xxxCommand del DataAdapter Usare l’oggetto CommandBuilder per costruire gli oggetti Command a run time Facile ma con un significativo overhead a run-time Limitato a SELECT su di una singola tabella Usare Visual Studio per generare il codice a design time Facile e senza nessun overhead a run-time Limitato a SELECT su di una singola tabella Codificarlo a mano nel programma a design time Difficile ma con il più alto livello di controllo ed efficenza Nessuna limitazione Popolare un DataSet I DataAdapters hanno la proprietà SelectCommand Specifica il comando SQL od il nome della stored procedure name per ottenere il result set private void Demo() { SqlDataAdapter da = new SqlDataAdapter("SELECT City FROM Authors","Server=localhost; Database=Pubs”); DataSet ds = new DataSet(); da.Fill( ds, "Authors" ); // Apre e chiude la connessione foreach ( DataRow dr in ds.Tables[ "Authors" ].Rows ) Console.WriteLine( dr[ "City" ] ); // Mostra le città } Utilizzare l’oggetto DataSet Un DataSet può: Memorizzare i dati in più DataTable relazionate tra loro Modellare le relazioni tra più DataTable Gestire i vincoli Fornisce viste per ordinamenti e filtri sui dati Aggiornare i dati Seguire i cambiamenti del DataSet Una DataRow può mantenere, per ogni colonna, versioni multiple di un valore : DataRowVersion.Current Il valore corrente della colonna DataRowVersion.Original Il valore della colonna prima dei cambiamenti DataRowVersion.Proposed Il valore di una colonna durante un ciclo di BeginEdit / EndEdit DataRowVersion.Default Il valore di default per lo stato della riga (es. Original per le righe cancellate in quanto queste non hanno un valore Current) Aggiungere/Cancellare Righe Aggiungere una riga ad una tabella con il metodo Add della proprietà Rows Il metodo NewRow crea una nuova riga ma non la aggiunge alla tabella Cancellare una riga con il metodo Delete Delete imposta lo stato della riga a Deleted, ma non la rimuove dalla tabella Per rimuoverla chiamare il metodo Remove della proprietà Rows Usare con cautela !!! Le righe rimosse non sono elaborate in fase di aggiornamento dal DataAdapter Row State Ogni riga ha la proprietà RowState Added, Deleted, Detached, Modified, Unchanged Fornisce una specie di “storia” della riga Il metodo AcceptChanges effettua i cambiamenti RowState è impostato ad Unchanged La versione Current della riga viene copiata su quella Original Il metodo RejectChanges ripristina i valori originali RowState è impostato ad Unchanged La versione Original della riga viene copiata su quella Current Esempio di Row State CURRENT ORIGINAL ROW STATE White White Unchanged Brown White Modified Brown Brown Unchanged dr["au_lname"] = "Brown"; dr.AcceptChanges(); Aggiornamenti via DataAdapter Gli aggiornamenti sono scritti nel database utilizzando il metodo Update del DataAdapter DataAdapter controlla il RowState di ogni riga Esegue le giuste azioni (insert, update o delete) in base allo stato della riga Usa gli oggetti Command assegnati mediante le proprietà InsertCommand, UpdateCommand e DeleteCommand DataRows nel DataTable DataAdapter Action RowState = Modified Use UPDATE command RowState = Unchanged Ignore RowState = Added Use INSERT command RowState = Modified Use UPDATE command RowState = Deleted Use DELETE command Ottimizzare gli Aggiornamenti Sia i DataSets che i DataTables supportono il metodo GetChanges GetChanges senza argomenti Estrae tutte le righe nelle quali RowState non è Unchanged Più efficenza se si deve passarlo a qualcosa dsChanges = ds.GetChanges(); GetChanges con l’argomento RowState Estrae solo quelle righe con il RowState specificato Consente di controllare l’ordine di applicazione delle operazioni di insert, update e delete nel database changes = ds.GetChanges( DataRowState.Added ); Si può utilizzare il metodo Merge per applicare i risultati di un aggiornamento Usare le Transazioni Esempio di un approccio “tutto-o-niente”: La Connezzione è utilizzata per avviare la transazione I Comandi sono eseguiti nella transazione Vengono eseguiti gli aggiornamenti La Transazione è committed oppure rolled back private void Save( DataSet ds, SqlDataAdapter da ) { SqlConnection con = da.InsertCommand.Connection; con.Open(); SqlTransaction tran = con.BeginTransaction(); da.InsertCommand.Transaction = tran; da.UpdateCommand.Transaction = tran; da.DeleteCommand.Transaction = tran; try { da.Update( ds, "Authors" ); tran.Commit(); } catch { tran.Rollback(); throw; } finally { con.Close(); } } Dataset Tipizzati DataSet Tipizzati Visual Studio 2005 può generare DataSets tipizzati Classi Custom derivate dalla classe DataSet Forniscono tutte le funzionalità del DataSet Possiede classi, metodi, eventi e proprietà specifiche per i dati contenuti Un DataSet tipizzato può contenere: Una classe chiamata Products derivata da DataTable Una classe chiamata ProductRow derivata da DataRow con le proprietà ProductID e Name Vantaggi dei DataSets Tipizzzati I DataSets Tipizzati offrono numerosi vantaggi Controllo del tipo a Compile-time Funzionalità aggiuntive Per esempio un metodo FindByProductsID Codice più leggibile e manutenibile string s = productDataSet.Products[ 1 ].Name; invece di: string s = (string) productDataSet.Tables [ "Products" ].Rows[ 1 ].Item[ "Name" ]; Supporto all’IntelliSense Creare un DataSet Tipizzato Visual Studio genera i DataSet tipizzati da uno schema di definizione XML (XSD) Visual Studio offre molti modi per generare lo schema XML Il modo più semplice è mediante l’ambiente di sviluppo Quindi… Don’t worry… b.net !!! Visitate: www.edudotnet.it scriveteci a: [email protected] [email protected] {paolo, delfo}@edudotnet.it