Le avventure in VB.Net di un principiante ex-VB6 - 8
a cura di Oscar Zanin e Diego Cattaruzza (requisiti: Visual Basic Express e SqlServer Express )

Premessa
Prima di trattare delle classi di gestione dei dati, si ritiene importante fare qualche cenno sulla tecnologia di accesso ai dati ADO.Net, che è quella che verrà utilizzata nella nostra applicazione di esempio

Di recente, con le nuove versioni del Framework .Net, sono state rese disponibili altre tecnologie di interazione con i dati, come Link, Entity Framework, Data Services.

E' vero, rischiamo di partire... già in ritardo, ma crediamo che sia più conveniente, per chi proviene da VB6, approcciare un sistema più familiare, prima di adottare, una volta acquisita una certa dimestichezza con 'il modo di pensare .Net', gli strumenti più moderni.

Questo articolo è prevalentemente di carattere teorico, perché si propone di fornire qualche nozione schematica di base su ADO.Net.
Si tratta di pochi cenni teorici ad ADO.Net in generale e ad alcuni suoi oggetti. Sono stati scritti libri interi sull'argomento ed è proprio da uno di questi, "Programmare ADO.Net" di David Sceppa, che si riporta quanto segue.

ADO.Net
ADO.Net è un insieme di librerie incluse con Microsoft NET Framework che aiuta a comunicare con diversi archivi di dati da applicazioni .Net. Le librerie ADO.Net comprendono classi per connettere a un'origine dati, sottoporre query ed elaborare risultati. E' inoltre possibile utilizzare ADO.Net come una memoria di dati non in linea, gerarchica e robusta, da utilizzare per gestire dati non in linea. L'oggetto centrale non connesso, il DataSet, permette di ordinare, ricercare, filtrare, archiviare modifiche in sospeso e di spostarsi in gerarchie di dati. Il DataSet comprende inoltre un numero di funzionalità che accorciano le distanze tra l'accesso ai dati tradizionale e lo sviluppo XML. Gli sviluppatori sono ora in grado di gestire dati XML tramite interfacce di accesso ai dati tradizionali e viceversa.

Molti sviluppatori che non conoscono ancora .Net Framework potrebbero avere esperienza con la precedente tecnologia di accesso ai dati di Microsoft, Active Data Object (ADO). Per quanto molto utile per gli sviluppatori, ADO non contiene le funzionalità fondamentali di cui gli sviluppatori hanno bisogno per generare applicazioni più potenti. Ad esempio, sempre più sviluppatori desiderano lavorare con dati XML. Nonostante le versioni successive di ADO aggiungano varie funzionalità XML, ADO non era progettato per gestire dati XML. Ad esempio, non consente di separare le informazioni sullo schema dai dati veri e propri. Microsoft potrebbe aggiungere ulteriori funzionalità XML alle prossime versioni di ADO, ma ADO non sarà mai in grado di gestire dati XML con la stessa efficacia di ADO.Net perchè ADO.Net è stato progettato a questo scopo, al contrario di ADO. Il motore del cursore ADO rende possibile il passaggio di oggetti Recordset non connessi ADO da un livello all'altro dell'applicazione, ma non è possibile combinare i contenuti di più oggetti Recordset. ADO consente di inviare le modifiche memorizzate nella cache ai database, ma non consente il controllo sulla logica utilizzata per inviare gli aggiornamenti. Inoltre, il motore di cursore ADO non fornisce, ad esempio, un modo per inviare modifiche in sospeso al database tramite stored procedure. Poiché molti amministratori di database consentono agli utenti di modificare i contenuti del database solo tramite stored procedure, gli sviluppatori non sono in grado di inviare aggiornamenti tramite l'oggetto Recordset ADO.
Microsoft ha progettato ADO.Net al fine di gestire questi scenari fondamentali. ADO.Net è progettato per combinare le funzionalità migliori delle versioni precedenti con funzionalità aggiuntive richieste frequentemente dagli sviluppatori, quali maggior supporto XML, più facile accesso ai dati non in linea, maggiore controllo sugli aggiornamenti e maggiore flessibilità nell'aggiornamento.

Il modello-oggetto di ADO.Net
Dopo avere descritto lo scopo di ADO.Net e il relativo ruolo all'interno dell'architettura complessiva di Visual Studio, occorre esaminarne più in dettaglio la tecnologia. Di seguito, verrà descritto brevemente il modello-oggetto di ADO.Net e ne verranno illustrate le differenze rispetto alle tecnologie di accesso ai dati Microsoft precedenti.
ADO.Net è progettato per aiutare gli sviluppatori a compilare applicazioni multilivello efficaci attraverso reti Intranet e Internet, grazie al modello-oggetto di ADO.Net.

Nella figura vengono illustrate le classi che comprendono il modello-oggetto di ADO.Net.
La linea tratteggiata divide in due il modello-oggetto.
Gli oggetti a sinistra della linea sono oggetti connessi.
Questi comunicano direttamente con il database per gestire la connessione e le transazioni, così come per recuperare e inviare i dati da e verso il proprio database.
Gli oggetti a destra della linea sono oggetti non connessi che consentono all'utente di gestire i dati non in linea.
Gli oggetti non connessi del modello-oggetto di ADO.Net non comunicano direttamente con gli oggetti connessi. Questa è una delle più grandi modifiche rispetto ai modelli-oggetto-di-accesso-ai-dati di Microsoft.
In ADO, l'oggetto Recordset memorizza i risultati delle query. E' possibile chiamare il metodo Open per recuperare i risultati di una query e chiamare il metodo Update (o UpdateBatch) per inviare le modifiche memorizzate all'interno del Recordset al proprio database.

Il DataSet ADO.Net, che verrà brevemente illustrato, è paragonabile, per quanto riguarda la funzionalità, al Recordset ADO. Comunque, il DataSet non comunica con il database. Per recuperare i dati dal database al DataSet, si passa il DataSet nel metodo Fill di un oggetto ADO.Net connesso, il DataAdapter. Allo stesso modo, per inviare le modifiche in sospeso nel DataSet al proprio database si passa il DataSet nel metodo Update dell'oggetto DataAdapter.

Provider dati .Net
Un provider dati .Net è un insieme di classi progettate per consentire all'utente di comunicare con un tipo particolare di archivi dati. Per tali provider, .Net Framework include i provider dati SQL, Oracle, ODBC e OLE DB. I provider dati SQL Client e Oracle Client sono progettati per comunicare con database specifici, rispettivamente, SQL Server e Oracle. I provider dati .Net ODBC e OLE DB sono spesso chiamati componenti "ponte", perché fungono da tramite per le tecnologie legacy ODBC e OLE DB. Questi provider consentono di comunicare con diversi archivi di dati tramite, rispettivamente driver ODBC e provider OLE DB.
Ogni provider dati .Net implementa le medesime classi di base: ProviderFactrory, Connection, ConnectionStringBuilder, Command, DataReader, Parameter e Transaction, nonostante i relativi nomi dipendano dal provider. Ad esempio, il provider dati .Net SQL Client comprende una classe SqlConnection, mentre il provider dati NET ODBC comprende una classe OdbcConnection.
Indipendentemente dal provider dati .Net utilizzato, la classe Connection del provider implementa le stesse funzionalità di base attraverso le medesime interfacce di base. Per avviare una connessione al proprio archivio di dati, è necessario creare un'istanza della classe di connessione del provider, impostare la proprietà ConnectionString dell'oggetto, quindi chiamare il relativo metodo Open.
Ogni provider dati .Net ha il proprio spazio dei nomi. I quattro provider compresi in .Net Framework sono sottoinsiemi dello spazio dei nomi System.Data, dove risiedono gli oggetti non connessi. Il provider dati .Net SQL Client risiede nello spazio dei nomi System.Data.SqlClient, il provider dati .Net ODBC risiede in System.Data.Odbc, il provider dati .Net OLE DB risiede in System.Data.OleDb e il provider dati .Net Oracle Client risiede in System.Data.OracleClient.

Oggetti connessi
Il modello-oggetto di ADO.Net comprende classi progettate per aiutare a comunicare direttamente con l'origine dei dati. Si farà riferimento a tali classi, che vengono visualizzate a sinistra della linea tratteggiata nella figura (precedentemente illustrata), come a classi "connesse" di ADO.Net. La maggior parte si fonda su concetti di base di accesso ai dati, come la connessione fisica al database, una query e il risultato della query.

Classe ProviderFactory
La classe ProviderFactory è nuova in ADO.Net 2.0 e funge da factory dell'oggetto, consentendo di creare istanze di altre classi per il proprio data provider .Net. Ogni classe ProviderFactory offre un metodo Create che crea connessioni, generatori di stringhe di connessione, comandi, parametri, adattatori dati e generatori di comandi.

Classe Connection
L'oggetto Connection rappresenta una connessione alla propria origine di dati. E' possibile specificare il tipo di origine di dati, il relativo percorso e altri attributi tramite le diverse proprietà della classe Connection. L'oggetto Connection è a grandi linee equivalente all'oggetto Connection ADO o all'oggetto Database DAO; si utilizza per connettersi al e disconnettersi dal database. L'oggetto Connection funge da condotto tramite il quale altri oggetti, come gli oggetti DataAdapter e Command, comunicano con il proprio database per inviare query e risultati relativi.

Classe ConnectionStringBuilder
La classe ConnectionStringBuilder è nuova in ADO.Net 2.0 e semplifica il processo di generazione di stringhe di connessione per un provider dati .Net. Ogni classe ConnectionStringBuilder presenta proprietà che corrispondono alle opzioni disponibili nelle relative stringhe di connessione del provider dati .Net. Ad esempio, la classe OdbcConnectionStringBuilder espone la proprietà Driver e la classe OleDbConnectionStringBuilder espone la proprietà Provider. Dopo avere utilizzato il ConnectionStringBuilder per generare la propria stringa di connessione, è possibile accedere a quest'ultima utilizzando la proprietà ConnectionString dell'oggetto ConnectionStringBuilder.

Classe Command
Gli oggetti Command sono simili in struttura agli oggetti Command ADO o QueryDef DAO. Sono in grado di rappresentare una query nei confronti del database, una chiamata a una stored procedure o una richiesta diretta di recuperare i contenuti di una tabella specifica. I database supportano molti tipi di query. Alcune query recuperano righe di dati facendo riferimento a una o più tabelle o visualizzazioni, oppure chiamando una stored procedure. Altre query modificano le righe di dati, altre ancora manipolano la struttura del database creando o modificando oggetti come tabelle, visualizzazioni o stored procedure. E' possibile utilizzare un oggetto Command per eseguire questi tipi di query nei confronti del proprio database. Utilizzare un oggetto Command per interrogare il proprio database è un'operazione piuttosto diretta. Innanzitutto, si imposta la proprietà Connection in un oggetto Connection che si connette al proprio database, quindi si specifica il testo della query nella proprietà CommandText. E' possibile elaborare una query SQL standard, come la seguente:

  SELECT CustomerID, CompanyName, ContactName, Phone FROM Customers

Oppure, è possibile fornire solo il nome di una tabella, visualizzazione o stored procedure e utilizzare la proprietà CommandType dell'oggetto Command per il tipo di query che si desidera eseguire. La classe Command offre diversi modi di eseguire query. Se la query non dà come risultato delle righe, conviene chiamare il metodo ExecuteNonQuery. La classe Command presenta inoltre un metodo ExecuteReader, che restituisce un oggetto DataReader che è possibile utilizzare per esaminare le righe restituite dalla query. Se si desidera restituire la prima colonna della prima riga restituita dalla query, è possibile evitare di scrivere qualche riga di codice e chiamare invece il metodo ExecuteScalar dell'oggetto Command. Il comando SqlCommand include un quarto metodo di esecuzione, il metodo ExecuteXmlReader simile a ExecuteReader ma progettato per gestire query che restituiscono risultati in formato XML.

Classe DataReader
La classe DataReader è progettata per aiutare a recuperare ed esaminare le righe restituite dalla query il più rapidamente possibile. E possibile utilizzare la classe DataReader per esaminare i risultati della query una riga per volta. Quando si passa alla riga successiva, i contenuti della riga precedente vengono ignorati. Il DataReader non supporta l'aggiornamento. I dati restituiti dal DataReader sono di sola lettura. Poiché la classe DataReader supporta così poche funzionalità, è estremamente rapida e leggera. Gli sviluppatori con esperienza che utilizzano i cursori nelle precedenti tecnologie di accesso ai dati potrebbero paragonare il DataReader con il cursore di sola lettura a scorrimento in avanti, o con il cursore firehose.

Classe Transaction
A volte, potrebbe essere necessario raggruppare un certo numero di modifiche apportate al proprio database e trattarle come una singola unità di lavoro. Nella programmazione di database, tale unità di lavoro è definita transazione. ...(omissis)
La classe Connection presenta un metodo BeginTransaction che può essere utilizzato per creare oggetti Transaction. Si utilizza un oggetto Transaction per effettuare o annullare le modifiche apportate al database per tutta la durata dell'oggetto Transaction.

Classe Parameter
Si ipotizzi di volere interrogare la tabella degli ordini relativamente a tutti gli ordini provenienti da un determinato cliente. La query assomiglierà alla seguente:

  SELECT CustomerID, CompanyName , ContractName , Phone FROM Customers
  WHERE CustomerID = 'ALFKI'

Il valore utilizzato per la colonna CustomerID nell'istruzione WHERE della query dipende dal cliente del quale si desiderano esaminare gli ordini. Tuttavia, se si utilizza questo tipo di query, è necessario modificare il testo della query ogni volta che si desidera esaminare gli ordini di un altro cliente.
Per semplificare il processo di esecuzione di query, è possibile sostituire il valore per la colonna CustomerID con un indicatore di parametro, come illustrato nella query seguente:

  SELECT CustomerID, CompanyName, CompanyName, Phone FROM Customers
  WHERE CustomerID = @CustomerID

Quindi, prima di eseguire la query, si fornisce un valore per il parametro. Molti sviluppatori si affidano completamente alle query con parametro perchè queste ultime sono in grado di semplificare la programmazione e contribuire a un codice più efficiente.
Per utilizzare un oggetto Command parametrizzato, si creano oggetti Parameter per ogni parametro della query e si accodano all'insieme Parameters dell'oggetto Command. La classe Parameter ADO.Net espone metodi e proprietà che rendono possibile definire il tipo di dati e il valore dei propri parametri. Per utilizzare una stored procedure che restituisca i risultati tramite parametri in uscita, impostare la proprietà Direction dell'oggetto Parameter nel valore appropriato dall'enumerazione ParameterDirection.

Classe DataAdapter
La classe DataAdapter rappresenta un nuovo concetto per i modelli di accesso ai dati Microsoft; non trova equivalenti in ADO o DAO, nonostante sia possibile considerare gli oggetti Command ADO e QueryDef DAO piuttosto simili a questa classe.
Gli oggetti DataAdapter fungono da tramite tra il database e gli oggetti non connessi nel modello-oggetto di ADO.Net. Il metodo Fill che è parte della classe di oggetti DataAdapter fornisce un sistema efficiente per recuperare i risultati di una query in un DataSet o in una DataTable, in modo da potere gestire i dati non in linea. E' inoltre possibile utilizzare gli oggetti DataAdapter per inviare le modifiche in sospeso memorizzate negli oggetti DataSet al proprio database.
La classe DataAdapter ADO.Net espone un determinato numero di proprietà che sono, in realtà, oggetti Command. Ad esempio, la proprieta SelectCommand contiene un oggetto Command che rappresenta la query che si utilizzerà per compilare il proprio oggetto DataSet. La classe DataAdapter presenta inoltre le proprietà UpdateCommand, InsertCommand e DeleteCommand, che corrispondono agli oggetti Command che si utilizzano per inviare, rispettivamente, righe modificate, nuove o eliminate al proprio database.
Questi oggetti Command forniscono una funzionalità di aggiornamento che era automatica (o magica, secondo alcuni) negli oggetti Recordset ADO e DAO. Ad esempio, quando si esegue una query in ADO per generare un oggetto Recordset, il motore del cursore ADO chiede ai database i metadati sulla query per determinare la provenienza dei risultati. Quindi, ADO utilizza i metadati per generare la logica di aggiornamento per convertire le modifiche dell'oggetto Recordset in modifiche del database.
Allora perchè la classe DataAdapter ADO.Net ha proprietà UpdateCommand, InsertCommand e DeleteCommand separate? Per consentire all'utente di definire la propria logica di aggiornamento. La funzionalità di aggiornamento in ADO e DAO è piuttosto limitata, nel senso che entrambi i modelli oggetto convertono le modifiche degli oggetti Recordset in query di comando che fanno direttamente riferimento alle tabelle del database.
Per preservare la sicurezza e l'integrità dei dati, molti amministratori di database limitano l'accesso alle tabelle dei propri database, in modo che l'unico modo di modificare i contenuti di una tabella sia chiamare una stored procedure. ADO e DAO non sono in grado di inviare modifiche utilizzando una stored procedure e non forniscono sistemi per consentire all'utente di specificare la propria logica di aggiornamento. Invece, DataAdapter di ADO.Net offre queste funzionalità.
Con l'oggetto DataAdapter è possibile impostare le proprietà UpdateCommand, InsertCommand e DeleteCommand per chiamare le stored procedure che modificheranno, aggiungeranno o elimineranno righe nella tabella appropriata del proprio database. Quindi, sarà sufficiente chiamare il metodo Update sull'oggetto DataAdapter, e ADO.Net utilizzerà gli oggetti Command creati per inviare le modifiche memorizzate nella cache nel DataSet al proprio database.
Come già sottolineato, la classe DataAdapter compila le tabelle nell'oggetto DataSet, legge le modifiche memorizzate nella cache, per poi inviarle al database. Per tenere traccia del percorso degli elementi salvati, DataAdapter presenta alcune proprietà di supporto. L'insieme TableMappings è una proprietà utilizzata per registrare quale tabella nel database corrisponde a ogni tabella nell'oggetto DataSet. Il mapping di ogni tabella presenta la stessa proprietà per eseguire il mapping delle colonne, definite insiemi ColumnMappings.

Classi non connesse
Come descritto, è possibile utilizzare le classi connesse in un provider dati .Net per connettersi a un'origine di dati, inviare query ed esaminarne i risultati. Comunque, queste classi connesse consentono di esaminare dati solo come flusso di dati in sola lettura a scorrimento in avanti. E se invece si desidera ordinare, ricercare, filtrare o modificare i risultati delle query?
Il modello-oggetto di ADO.Net comprende classi che forniscono questa funzionalità. Queste classi funzionano come una memoria di dati non in linea. Dopo avere recuperato i risultati della query in una DataTable (che verrà descritta tra breve), è possibile interrompere la connessione all'origine di dati e continuare a gestire i dati. Come già menzionato, poiché tali classi non richiedono una connessione attiva all'origine di dati, vengono definite classi "non connesse". In seguito verranno considerate le classi non connesse nel modello-oggetto di ADO.Net.

Classe DataTable
La classe DataTable ADO.Net è simile alle classi Recordset ADO e DAO. L'oggetto DataTable consente di esaminare dati tramite insiemi di righe e colonne. E' possibile esaminare i risultati di una query in una DataTable chiamando il metodo Fill dell'oggetto DataAdapter, come illustrato nel seguente frammento di codice:

    Dim strConn, strSQL As String
    strConn = "Data Source=.\SQLExpress ; " & _
              "Initial Catalog=Northwind ; Integrated Security=True; "
    strSQL = "SELECT CustomerID, CompanyName FROM Customers"
    
    Dim da As New SqlDataAdapter(strSQL, strConn)
    Dim tbl As New DataTable()
    da.Fill(tbl)

Dopo avere recuperato i dati dai database e averli memorizzati in un oggetto DataTable, tali dati non sono in linea col server. E' possibile quindi esaminare i contenuti dell'oggetto DataTable senza creare nessun traffico di rete tra ADO.Net e il database. Gestendo i dati non in linea, non è più necessaria una connessione attiva con il database; tuttavia, in questo modo le modifiche apportate da altri utenti dopo avere eseguito la query non saranno visibili.
La classe DataTable contiene insiemi di altri oggetti non connessi, che verranno descritti più avanti. Si accede al contenuto di una DataTable tramite la proprietà Rows, che restituisce un insieme di oggetti DataRow. Se si desidera esaminare la struttura di una DataTable, si utilizza la proprietà Columns per recuperare un insieme di oggetti DataColumn. La classe DataTable consente inoltre di definire vincoli, ad esempio una chiave primaria, relativamente ai dati archiviati nella classe. E' possibile accedere a tali vincoli tramite la proprietà Constraints dell'oggetto DataTable.

Classe DataColumn
Ogni DataTable contiene un insieme Columms, ovvero un contenitore di oggetti DataColumn. Come suggerisce il nome, l'oggetto DataColumn corrisponde a una colonna della tabella.
Comunque, un oggetto DataColumn non contiene effettivamente i dati archiviati nella DataTable ma memorizza le informazioni sulla struttura della colonna. Informazioni di questo tipo, dati su dati, sono solitamente definite metadati. Ad esempio, la classe DataColumn presenta una proprietà DataType che descrive il tipo di dati (ad esempio stringhe o numeri interi) contenuti nella colonna. La classe DataColumn presenta altre proprietà, come ReadOnly, AllowDBNull, Unique, Default e Autolncrement, che consentono di verificare se i dati nella colonna possono essere aggiornati, definiscono limiti sul contenuto della colonna o determinano come generare i valori per nuove righe di dati.
La classe DataColumn presenta inoltre la proprietà Expression, che può essere utilizzata per definire come calcolare i dati nella colonna. Una pratica comune è di basare una colonna su una query o un'espressione piuttosto che sul contenuto di una colonna di una tabella del database. Ad esempio, nel database Northwind di esempio che accompagna la maggior parte dei prodotti legati al database Microsoft, ogni riga della tabella Order Details contiene le colonne UnitPrice e Quantity. Normalmente, per esaminare il costo totale di un ordine, era necessario aggiungere alla query una colonna calcolata. Il seguente esempio SQL definisce una colonna calcolata chiamata ItemTotal:

  SELECT 0rderID, ProductID, Quantity, UnitPrice,
  Quantity * UnitPrice AS ItemTotal
  FROM [Order Details]

Lo svantaggio di questa tecnica è che il motore del database effettua il calcolo solo al momento della query. Se si modificano i contenuti delle colonne UnitPrice o Quantity nell'oggetto DataTable, la colonna ItemTotal non subirà modifiche.
La classe DataColumn di ADO.Net definisce la proprietà Expression per gestire meglio questo scenario. Quando si controlla il valore di un oggetto DataColumn basato su un'espressione, ADO.Net valuta l'espressione e restituisce un valore nuovamente calcolato. In questo modo, se si aggiorna il valore di ogni colonna nell'espressione, il valore contenuto nella tabella calcolata sarà sempre esatto. Di seguito viene riportato un frammento di codice che illustra l'utilizzo della proprietà Expression:

    With col
      .ColumnName = "ItemTotal"
      .DataType = GetType(Decimal)
      .Expression = "UnitPrice * Quantity"
    End With

L'insieme Columns e gli oggetti DataColumn possono pressappoco essere paragonati all'insieme Fields e agli oggetti Field in ADO e DAO.

Classe Constraint
La classe DataTable fornisce inoltre un modo per porre vincoli sui dati memorizzati localmente all'interno dell'oggetto DataTable. Ad esempio, è possibile creare un oggetto Constraint che assicuri che i valori in una colonna, o in più colonne, siano unici all'interno di DataTable. Gli oggetti Constraint vengono conservati in un insieme Constraints dell'oggetto DataTable.

Classe DataRow
Per accedere ai valori correnti archiviati in un oggetto DataTable, è possibile utilizzare l'insieme Rows, che contiene una serie di oggetti DataRow. Per esaminare i dati archiviati in una determinata colonna di una determinata riga, è possibile utilizzare la proprietà Item dell'oggetto DataRow appropriato per visualizzare il valore in ogni colonna di quella riga. La classe DataRow fornisce diverse definizioni di overload della relativa proprietà Item. E' possibile specificare la colonna da visualizzare trasmettendo il nome della colonna, il valore indice o l'oggetto DataColumn associato, alla proprietà Item dell'oggetto DataRow. Poichè Item è la proprietà predefinita della classe DataRow, è possibile utilizzare tale proprietà senza specificarlo, come illustrato nel seguente frammento di codice:

    Dim col  As New DataColumn()
    '...
    Dim row As DataRow
    row = tbl.Rows(0)
    Console.WriteLine(row(0))
    Console.WriteLine(row("CustomerID"))
    Console.WriteLine(row(tbl.Columns("CustomerID")))

DataTable rende disponibili non solo i dati della riga corrente, ma di tutte le righe di dati, tramite un insieme dl DataRows. Ciò rappresenta una modifica notevole rispetto agli oggetti Recordset ADO e DAO, che restituiscono solo una riga di dati alla volta, rendendo quindi indispensabile l'esplorazione dei relativi contenuti utilizzando metodi come MoveNext. Il seguente frammento di codice è un esempio di loop nel contenuto di un Recorset ADO:

    'Visual Basic 'Classic'
    Dim strConn As String, strSQL As String
    Dim rs As ADODB.Recordset
    strConn = "Provider=SQLOLEDB;Data Source=.\SQLExpress;" & _
              "Initial Catalog=Northwind;Integrated Security=SSPI;"
    strSQL = "SELECT CustomerID, CompanyName FROM Customers"
    rs = New ADODB.Recordset
    rs.CursorLocation = adUseClient
    rs.Open(strSQL, strConn, adOpenStatic, adLockReadOnly, adCmdText)
    Do While Not rs.EOF
      Debug.Print(rs("CustomerID"))
      rs.MoveNext()
    Loop

Per esaminare i contenuti di un DataTable di ADO.Net, scorrere gli oggetti DataRow contenuti nella proprietà Rows dell'oggetto DataTable, come illustrato nel seguente frammento di codice:

    'Visual Basic Net
    Dim strSQL, strConn As String
    strConn = "Data Source=.\SQLExpress;" & _
              "Initial Catalog=Northwind;Integrated Security=True;"
    strSQL = "SELECT CustomerID, CompanyName FROM Customers"
    Dim da As New SqlDataAdapter(strSQL, strConn)
    Dim tbl As New DataTable()
    da.Fill(tbl)
    For Each row As DataRow In tbl.Rows
      Console.WriteLine(row("CustomerID"))
    Next row

La classe DataRow rappresenta inoltre il punto di partenza degli aggiornamenti. Ad esempio, è possibile chiamare il metodo BeginEdit di un oggetto DataRow, modificare il valore di alcune colonne di una determinata riga tramite la proprietà Item, chiamare il metodo EndEdit per salvare le modifiche a quella riga. Chiamare il metodo CancelEdit di un oggetto DataRow consente di cancellare le modifiche apportate durante la sessione di modifiche più recente. La classe DataRow espone inoltre i metodi per eliminare o rimuovere un elemento dall'insieme di DataRow dell'oggetto DataTable.
Quando si modifica il contenuto di una riga, DataRow memorizza nella cache tali modifiche, in modo da poterle poi inviare al database. Quindi, quando si modifica il valore di una colonna in una riga, DataRow conserva tanto il valore originale di quella colonna quanto il valore corrente, in modo da aggiornare efficacemente il database. La proprietà Item di un oggetto DataRow consente inoltre di esaminare il valore originale di una colonna quando la riga ha una modifica in sospeso.

Classe DataSet
Un oggetto DataSet, come indica il nome, contiene un insieme di dati. E' possibile pensare a un oggetto DataSet come al contenitore di vari oggetti DataTable (memorizzati nell'insieme Tables dell'oggetto DataSet). ADO.Net è stato creato per aiutare gli sviluppatori a compilare applicazioni di database multi-tier. A volte, può essere necessario accedere a un componente in esecuzione su un server di livello intermedio per recuperare i contenuti di più tabelle. Invece di dovere chiamare ripetutamente il server per recuperare i dati una tabella per volta, e possibile impacchettare tutti i dati in un oggetto DataSet e recuperarli in una singola chiamata. Un oggetto DataSet, tuttavia, è molto più di un contenitore di oggetti DataTable. I dati memorizzati in un oggetto DataSet non sono connessi al database. Tutte le modifiche apportate ai dati vengono semplicemente memorizzati in ogni DataRow. Quando occorre inviare tali modifiche al database, potrebbe non essere comodo inviare l'intero DataSet al server di livello intermedio. E' possibile utilizzare il metodo GetChanges per estrarre solo le righe modificate dal DataSet. In questo modo, viene passata una minore quantità di dati tra diversi processi o server. La classe DataSet inoltre espone il metodo Merge, che funge da complemento al metodo GetChanges. Il server di livello intermedio utilizzato per inviare modifiche al proprio database, utilizzando il più piccolo DataSet restituito dal metodo Merge, potrebbe restituire un DataSet contenente dati appena recuperati. E' possibile utilizzare il metodo Merge della classe DataSet per combinare i contenuti di due oggetti DataSet in un unico DataSet. Anche questo esempio illustra che ADO.Net è stato sviluppato sulla base di applicazioni multi-tier. I modelli di accesso ai dati Microsoft non presentano funzionalità paragonabili.
E' possibile create un oggetto DataSet e compilare con delle informazioni il relativo insieme Tables senza necessariamente comunicare con un database. Nei precedenti modelli di accesso ai dati, solitamente è necessario interrogare un database prima di aggiungere localmente nuove righe, per poi inviarle al database. Con ADO.Net, non è necessario comunicare con il database fino a che non si intende aggiungere le nuove righe.
La classe DataSet inoltre presenta funzionalità che consentono la relativa scrittura e lettura da un file o un'area della memoria. E' possibile salvare solo i contenuti dell'oggetto DataSet, solo la struttura dell'oggetto DataSet, o entrambi. ADO.Net archivia questi dati come documento XML. Poiché ADO.Net e XML sono così collegati, lo spostamento di dati tra oggetti DataSet ADO.Net e documenti XML è semplicissimo. Quindi, è possibile sfruttare una delle funzionalità più potenti di XML: la capacità di trasformare facilmente la struttura dei dati. Ad esempio, è possibile utilizzare un modello di trasformazione XSL (Extensible Stylesheet Language) per convertire i dati esportati a un documento XML in HTML.

Classe DataRelation
Solitamente, le tabelle di un database sono in qualche modo legate. Ad esempio, nel database Northwind ogni voce nella tabella Orders è in relazione con una voce della tabella Customers, quindi è possibile determinare quale cliente ha effettuato quale ordine. E' possibile utilizzare questi collegamenti tra dati da più tabelle di un'applicazione. La classe DataSet ADO.Net gestisce dati da oggetti DataTable connessi con l'aiuto della classe DataRelation.
La classe DataSet espone una proprietà Relations, ovvero un insieme di oggetti DataRelation. E' possibile utilizzare un oggetto DataRelation per indicare una relazione tra diversi oggetti DataTable nel proprio DataSet. Una volta creato il proprio oggetto DataRelation, è possibile utilizzare codice simile al seguente per recuperare una matrice di oggetti DataRow per gli ordini che corrispondono a un particolare cliente:

    Dim ds As DataSet
    Dim tblCustomers, tblOrders As DataTabIe
    Dim rel As DataRelation
 
    'The code for creating the DataSet goes here.
 
    rel = ds.Relations.Add("Customers_0rders", _
          tblCustomers.Columns("CustomerID"), _
          tblOrders.Columns("CustomerID"))
 
    For Each rowCustomer As DataRow In tblCustomers.Rows
      Console.WriteLine(rowCustomer("CompanyName"))
      For Each rowOrder As DataRow In rowCustomer.GetChildRows(rel)
        Console.WriteLine(" {0}", rowOrder("OrderID"))
      Next rowOrder
      Console.WriteLine()
    Next rowCustomer

Gli oggetti DataRelation hanno inoltre proprietà che consentono di imporre integrità referenziale. Ad esempio, è possibile impostare un oggetto DataRelation in modo che, se si modifica il valore del campo della chiave primaria nella riga padre, la modifica venga effettuata automaticamente nelle righe figlio. E' inoltre possibile impostare l'oggetto DataRelation in modo che, se si elimina una riga in un DataTable, anche le righe corrispondenti in ogni oggetto figlio DataTable, come definito dalla relazione, vengano automaticamente eliminate.

Classe DataView
Una volta recuperati i risultati di una query in un oggetto DataTable, è possibile utilizzare un oggetto DataView per visualizzare i dati in diversi modi. Se si desidera ordinare il contenuto di un oggetto DataTable sulla base di una colonna, e sufficiente impostare la proprietà Sort dell'oggetto DataView col nome della colonna in questione. E' inoltre possibile impostare la proprietà Filter sulla DataView in modo che siano visibili solo le righe che corrispondono a determinati criteri.
E' possibile utilizzare più oggetti DataView per esaminare lo stesso DataTable nello stesso tempo. Ad esempio, è possibile avere due griglie in un modulo, una con l'elenco dei clienti in ordine alfabetico, l'altra con le righe ordinate in base a un altro campo, ad esempio lo stato o la regione. Per visualizzare entrambe, è necessario associare ogni griglia a un oggetto DataView diverso, ma entrambi gli oggetti DataView devono fare riferimento alla medesima DataTable. Questa funzionalità evita di dover conservare due copie dei dati in strutture separate.

Conclusione
Scopo di questo articolo è esporre sinteticamente ADO.Net e alcuni suoi oggetti in un unico 'luogo', senza vere le varie descrizioni sparse nei vari articoli in cui tali oggetti verranno utilizzati.
Inoltre, avere una prima visione d'insieme permette di comprendere meglio la struttura delle classi che verranno illustrate nelle prossime puntate.

(nota: qualche passo delle citazioni dal libro "Programmare ADO.Net" di David Sceppa è stato ri-tradotto per renderlo più comprensibile. Nonostante qualche lieve difetto di traduzione, il libro è da considerarsi fondamentale e imprescindibile nella biblioteca tecnica di qualsiasi sviluppatore .Net e lo si consiglia molto caldamente).