ADO .NET 2.0: what’s new Andrea Saltarello Software Architect – Managed Designs S.r.l. http://blogs.ugidotnet.org/pape Sponsor Parliamo di… • • • • Architettura ADO .NET 2.0 System.Data: what’s new SqlClient enhancements M.A.R.S. Agenda – Non parliamo di… ObjectSpaces • Inizialmente annunciato come parte di ADO .NET 2.0… • E’ stato poi “agganciato” al rilascio di WinFS (Longhorn)… • Ma WinFS è stato “tagliato” da Longhorn e posticipato a data da definirsi… • Insomma… non ne parliamo Prerequisiti per ADO .NET 2.0 • Non necessitano di MDAC: • Classi base, comuni (System.Data.Common) e disconnesse • .NET managed provider per SQL Server e Oracle • Usano MDAC, senza particolari requisiti: • Managed provider OLEDB e ODBC • Vanno bene le versioni 2.6, 2.7, 2.8 o… 9.0 Common Provider Model • ADO.NET v1.0/1.1 è basato su alcune interfacce • E’ problematico scrivere codice indipendente dalla base dati • ADO .NET 2.0 è basato su classi base condivise dai provider • E’ una estensione, non introduce incompatibilità • La sintassi SQL è comunque specifica per la base dati! • Architettura basata sul pattern Factory ADO .NET 2: Architettura IDb* interfaces (es: IDbConnection) Db* abstract base classes (es: DbConnection) Layer ProviderIndependent Db*Base implementation classes Sql OleDb ODBC Oracle 3rd 3rd Party 1 Party 2 Layer Providerspecific Classi Provider-independent Sono definite nel namespace System.Data.Common – ad esempio: DbCommand DbCommandBuilder DbConnection DataAdapter DbDataAdapter DbDataReader DbParameter DbParameterCollection DbTransaction DbProviderFactory DbProviderFactories DbException Usare una Provider Factory • Importare il Namespace: using System.Data.Common • Creare l’istanza della Factory: static DbProviderFactory factory = DbProviderFactories.GetFactory("provider-name") • Creare le istanze degli oggetti: DbConnection con = factory.CreateConnection() DbCommand cmd = con.CreateCommand() Provider Enumeration • Ogni provider ha un nome invariante – Per esempio: "System.Data.SqlClient", "System.Data.OracleClient" • Ottenere la lista delle provider factory installate DataTable dt = DbProviderFactories.GetFactoryClasses() DbProviderFactory factory = DbProviderFactories.GetFactory(dt.Rows[x]) – ... o ... DbProviderFactory factory = DbProviderFactories.GetFactory( dt.Select("InvariantName='System.Data.SqlClient'" ) [0]["InvariantName"].ToString()); Provider Enumeration usando una Provider Factory Performance Improvements • Indicizzazione interna delle righe. I tempi di: – Insert e delete crescono logaritmicamente – update rimangono quasi costanti • Serializzazione binaria – I DataSet v1.x erano sempre serializzati in XML • Vantaggioso per lo scambio dati, ma penalizzante per le performance – La v2.0 supporta la serializzazione binaria • È più veloce ed occupa meno spazio • Basta usare: DataSet.RemotingFormat = SerializationFormat.Binary Performance inserimento righe 4000 3500 3000 Time in Milliseconds 2500 2000 Everet t Whidbey 1500 1000 500 0 0 10000 20000 30000 40000 Number of Rows 50000 60000 70000 DataRow Insertion Perf Improvement End-to-End Time (lower is better) Binary vs. XML Serialization Xml Binary 100 1000 10000 Row s in DataSet Incrementi fino ad 80 x 100000 Popolare un DataSet • DataAdapter.Fill(DataSet, "table-name") – Nuovo: proprietà DataAdapter.FillLoadOption e AcceptChangesDuringUpdate • Nuovo: metodo DataSet.Load – Load(DataReader [, load-option] [, tablesarray]) • Nuovo: LoadOption enumeration – PreserveCurrentValues | UpdateCurrentValues | OverwriteRow LoadOption Enumeration RowState of Existing Row PreserveCurrentValues (the default value) UpdateCurrentValues OverwriteRow Added Current = Existing Original = Incoming RowState = Modified Current = Incoming Original = Existing RowState = Added Current = Incoming Original = Incoming RowState = Unchanged Modified Current = Existing Original = Incoming RowState = Modified Current = Incoming Original = Existing RowState = Modified Current = Incoming Original = Incoming RowState = Unchanged Deleted Current = Existing Original = Incoming RowState = Deleted * Undo Delete Current = Incoming Original = Existing RowState = Modified * Undo Delete Current = Incoming Original = Incoming RowState = Unchanged Unchanged Current = Incoming Original = Incoming RowState = Unchanged Current = Incoming Original = Existing * if new value = existing value: RowState = Unchanged * else: RowState = Modified Current = Incoming Original = Incoming RowState = Unchanged No matching existing row in the table Current = Incoming Original = Incoming RowState = Unchanged Current = Incoming Original = Existing RowState = Added Current = Incoming Original = Incoming RowState = Unchanged Nuove feature • RowState modificabile – Nuovi metodi: DataRow.SetAdded e DataRow.SetModified • Metodo DataSet.GetDataReader – Restituisce un DataTableReader – E’ possibile specificare quali tabelle includere Popolare un DataSet con un DataReader e usare un DataTableReader. DataTable “stand-alone” • Operazioni tipiche del DataSet supportate anche per le DataTable: – ReadXml, ReadXmlSchema, WriteXml, WriteXmlSchema, Clear, Clone, Copy, Merge, GetChanges • La classe DataTable ora supporta la serializzazione: – Perchè implementa IXmlSerializable – E’ possibile restituire una istanza di DataTable mediante Web Service o Remoting Popolare ed usare DataTable • DataAdapter.Fill(DataTable) • DataAdapter.Fill(DataTable[ ], …) – Permette di selezionare un sottoinsieme di righe • DataAdapter.Update(DataTable) • DataTable.Load(DataReader [, load-option] [, FillErrorEventHandler]) – Nuovi metodi: BeginLoadData, Load, EndLoadData • DataTable.GetDataReader method – Ottiene uno stream da una DataTable Update Batch • In 1.1, gli update dei DataSet sono eseguiti riga per riga • Il batch update riduce round-trip lungo la rete • DataAdapter.UpdateBatchSize = batch_size • Funziona all’interno di transazioni • Funziona con SQL Server 7.0, 2000, 2005 • Disponibile anche per il provider OracleClient DataTable “stand-alone” Comandi asincroni • Ideali per eseguire molteplici query • Il “solito” Async Pattern: Beginxxx e Endxxx • Supporta Polling, Wait e Callback • Non dovrebbe essere usato in abbinamento a MARS – usate una connessione per ogni Command • Aggiungere "async=true" alla connection string Esecuzione sincrona Application Rowset 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Connection Database1 Latency 3 secs Connection Database2 Latency 8 secs Connection Database3 Latency 5 secs Rowset 2 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Rowset 3 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Tempo per visualizzare i dati: ~ 16 3 secs 11 secs Esecuzione asincrona Application Rowset 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Connection1 Database1 Latency 3 secs Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Connection2 Database2 Latency 8 secs Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Connection3 Database3 Latency 5 secs Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Rowset 2 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Rowset 3 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Data 1 Tempo per visualizzare i dati: ~ 8 secs Esecuzione asincrona: Polling Model • Avviare un comando asincrono: Dim result As IAsyncResult = MyCommand.BeginExecuteReader() • Aspettare fino al termine della esecuzione: While Not result.IsCompleted ‘Codice da eseguire End While • Recuperare i risultati: Dim reader As SqlDataReader = MyCommand.EndExecuteReader(result ) Esecuzione asincrona: Wait (All) Model • Avviare uno o più comandi asincroni: Dim resultx As IAsyncResult = MyCommand.BeginExecuteReader() • Attendere il completamento di tutti i comandi: WaitHandle.WaitAll(New WaitHandle() {result1.AsyncWaitHandle, result2.AsyncWaitHandle, result3.AsyncWaitHandle}, timeout - ms, True) • Recuperare i risultati: Dim reader As SqlDataReader = MyCommand.EndExecuteReader(resultx) • Ideale per le Web Application ASP.NET Esecuzione asincrona: Wait (Any) Model • Avviare uno o più comandi asincroni come array di istanze di IAsyncResult: Dim resultx As IAsyncResult = MyCommand.BeginExecuteReader() • Attendere il completamento di ogni comando: Dim i As Integer For i = 0 To result_array.Length index = WaitHandle.WaitAny(result_array, timeout, true) Select Case index Case 0 Dim reader As SqlDataReader = MyCommand.EndExecuteReader(resultx) ... End Select End For Esecuzione asincrona: Callback Model • Avviare l’esecuzione, specificando la funzione di callback e il command come AsyncState: MyCommand.BeginExecuteReader(new AsyncCallback(MyCallback), cmd) • Creare il gestore del callback: Sub MyCallback(ByVal result As IAsyncResult) Dim cmd As SqlCommand = DirectCast(result.AsyncState, SqlCommand) Dim reader As SqlDataReader = cmd.EndExecuteReader(result) End Sub Esecuzione di comandi asincroni DataReader: Paging • Il paging? C’est plus facile, col metodo ExecutePageReader • Accetta l’indice della prima riga e il numero di righe da restituire • Non garantisce consistenza • Senza il supporto di una transazione, alcune righe potrebbero essere saltate o ripetute • Moooolto più veloce che leggere tutte le righe ed ignorare quelle non desiderate – E’ comunque preferibile eseguire una query “mirata” Troppo bello per essere vero? Lo pensa anche Microsoft, infatti ha rimosso il metodo nella beta2 Multiple Active Result Sets (MARS) • (Ri)Conoscete questo messaggio? – System.InvalidOperationException: There is already an open DataReader associated with this Connection which must be closed first. Multiple Active Results Sets (MARS) • Mantiene disponibile una connessione quando apriamo un SqlDataReader al fine di poter: – Eseguire un’altra query per ottenere un DataReader/XmlReader – Eseguire comandi DML • Possono essere attivi differenti result set contemporaeamente – alternare fetch ad ogni reader – Alternare query che non restituiscono reader Alternare Results Set • Scenario tipico: – Recuperare la lista dei clienti e scorrerla – Per ogni cliente, recuperare la lista degli ordini – Per ogni ordine, recuperarne il dettaglio • Nella v1, per ottenere ciò usando dei reader erano necessarie differenti connessioni • Mediante MARS, è sufficiente una sola connessione se: – I dati risiedono nello stesso database – Usiamo SQL Server 2005/MDAC9 Interleaved Results Sets Example Dim parentReader As DataReader = Command1.ExecuteReader() While parentReader.Read() ' process parent row data here ' then get rowset from child table Command2.Parameters("@id").Value = parentReader("id") Dim childReader As DataReader = Command2.ExecuteReader() ' process child rows here childReader.Close() End While parentReader.Close() Links http://www.ugidotnet.org http://forum.ugidotnet.org http://mobile.ugidotnet.org