Gestione di un DataBase Remoto

 

Premessa:

In questo breve articolo vengono introdotti molti argomenti interessanti, dalle funzionalità di ADO 2.5, all'utilizzo di pagine XML, allo sviluppo di applicativi client/server a tre livelli (three-tier). A trattarli tutti con dovizia, ne risulterebbe materiale sufficiente a scrivere un libro intero, quindi questo articolo non si propone come una guida completa a tali argomenti, ma li introduce e li tratta in maniera strettamente necessaria al fine di spiegare il funzionamento dei progetti d'esempio e di impostare un semplice applicativo per la gestione di un database remoto. Al fine di rendere il codice e l'esempio più semplice possibile, le funzionalità sono state limitate all'essenziale e alla fine verranno indicate possibili implementazioni e qualche consiglio su come realizzarle.

 

Introduzione:

Molto spesso, ad esempio nel caso di soluzioni di e-Commerce o siti web dinamici che si interfacciano ad un database, si ha la necessità di gestire un database residente su un server remoto, accessibile tramite il protocollo HTTP.

Supponiamo infatti che abbiamo un database residente sul server aziendale o su quello che ci fornisce hosting per il nostro sito e che abbiamo bisogno di aggiornarlo, mantenendo comunque una replica sul nostro PC locale: le soluzioni che mi vengono in mente sono:

  • back office: creare una serie di pagine asp residenti sullo stesso server, opportunamente nascoste e protette, che permettono l'aggiornamento del database (inserimento, modifica, eliminazione dei record e, volendo, anche modifiche alla stessa struttura del db). Questa soluzione è comoda nel caso in cui si voglia delegare gli aggiornamenti a persone diverse, però rende problematica la replica in locale (tranne un eventuale backup periodico).
  • FTP: aggiornare il database in locale tramite un front-end in Visual Basic e successivamente uploadare il db aggiornato sul server via FTP. Questa soluzione è la più semplice da implementare, ma comincia a diventare problematica sia nel caso in cui il db sia di grosse dimensioni ed il collegamento ad Internet non troppo veloce, sia nel caso in cui alcuni dati contenuti nel db siano stati modificati dagli utenti del sito (pensate ad un guestbook o ai contatori...). Una soluzione a questo secondo problema potrebbe essere la suddivisione dei dati "statici" (ovvero quelli modificati solo da noi) e quelli "dinamici" (ovvero quelli modificati direttamente dall'utente del sito) in due database distinti e collegando le varie tabelle.
  • Upload differenziale: utilizzare un front-end in Visual Basic che aggiorna il DB non utilizzando i metodi di ADO/DAO (AddNew, Delete, Edit/Update), bensì delle query SQL (INSERT, UPDATE, DELETE) che poi vengono memorizzate e successivamente inviate ad una pagina ASP che non fa altro che eseguirle sul DB residente sul server. Le cose si complicano un po' nel caso in cui cada la connessione durante un aggiornamento.

  • Però potremo anche aver bisogno di interfacciarci al database, quindi non semplicemente aggiornarlo, ma impostare come origine dati di un oggetto Recordset una tabella appartenente al db remoto, così da poter gestire il recordset come se si trattasse di un db locale, utilizzando l'interfaccia di ADO.
    In questo breve articolo vedremo appunto come realizzare un applicativo di questo tipo.

     

    Obiettivi:

    Realizzare un'applicativo client/server per l'interfacciamento ad un database remoto.
    Verranno sviluppate due applicazioni a tre livelli (three-tier) con funzionalità analoghe ma che adottano due tecnologie diverse:

  • Recordset XML: Il client ha come origine dati una pagina ASP residente sul server (livello intermedio) che interroga il database e restituisce una tabella formattate in XML che ADO converte in un oggetto Recordset da restituire al client. Discorso analogo per la registrazione delle modifiche.
  • DLL ActiveX: Il client comunica con un componente COM (nel nostro caso una DLL ActiveX) residente sul server che interroga il database e restituisce un oggetto ADO Recordset. Allo stesso modo la DLL riceve al client il recordset modificato con il quale aggiornare il database.

  •  

    Requisiti:

    In questo articolo e negli esempi allegati si utilizzerà un database in formato Access 97, ma si può facilmente trasportare il codice per gestire un db SQL Server o un altro DBMS.
    La versione di ADO installata sul client dev'essere la 2.5 (2.0 per il secondo esempio, quello che utilizza la DLL ActiveX) o successive, mentre il server dev'essere NT con IIS e bisogna impostare i permessi di lettura e scrittura alla directory contenente il database.
    Per quanto riguarda la DLL ActiveX, oltre a registrare tale componente sul client, è necessario specificare i riferimenti sul server, aggiungendo nel registro di configurazione la chiave DLLRemoteDB.clsRemoteDB al percorso:
    HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SVC\Parameters\ADCLaunch.

     

    I metodo - Recordset XML:

    Come procedere:

    Il progetto si divide in due componenti: il componente client ed il componente server. Il componente a livello client è l'applicazione Visual Basic che interrogherà il database, mentre la parte server include il livello intermedio, costituito da un insieme di pagine ASP che verranno utilizzate per generare il recordset da fornire al client, ed il livello fonte dati, costituito da un database Access. In pratica funziona così:
    Partiamo dal presupposto che non è possibile specificare nella stringa di connessione il percorso HTTP su cui risiede il database, altrimenti non avrebbe senso scrivere questo articolo. La soluzione è quella di impostare come origine dei dati di un recordset una pagina ASP che interroga il database e fornisce un recordset formattato in XML.
    Vediamolo in pratica.
    Sappiamo che per aprire un recordset con i dati di una tabella, in ASP scriveremo:

    <% 
    
    Dim rs
    Dim conn
    	
    Set rs = Server.CreateObject("ADODB.Recordset")
    Set conn = Server.CreateObject("ADODB.Connection")
    	
    conn.ConnectionString = "driver={Microsoft Access Driver (*.mdb)};dbq=" & _
    	Server.MapPath("example.mdb")
    conn.CursorLocation = adUseClient
    conn.Open 
    	
    rs.CursorLocation = adUseClient
    rs.Open "SELECT * FROM " & Request.QueryString("TableName"), conn, _
    	adOpenkeyset, adLockPessimistic
    
    %> 

    Il nome della tabella da aprire viene passata come parametro TableName; inoltre si noti che il cursore impostato per la connessione e per il recordset è adUseClient, mentre per il recordset deve essere impostato un blocco di tipo ottimistico a blocchi (adLockBatchOptimistic), al fine di poter fornire al client un recordset con i diritti di lettura/scrittura e non solo di lettura (come di default). Inoltre questi cursori sono consigliati nel caso di aggiornamenti batch poiché il record viene bloccato solo quando viene invocato l'aggiornamento e non per tutta la durata della modifica (il che sarebbe impossibile dal momento che la modifica la esegue il client sconnesso dal database).

    Una volta aperto il recordset, bisognerà in qualche modo renderlo disponibile al client, il quale (vedremo tra poco il codice) si aspetta che la pagina ASP gli restituisca un oggetto Recordset.
    Questo non è proprio possibile, per cui quello che possiamo fare è formattare il contenuto del recordset in XML e poi ci pensa ADO (dalla versione 2.1 in poi) a convertirlo in un oggetto Recordset vero e proprio.

    <% 
    
    Dim stm
    Set stm = Server.CreateObject("ADODB.Stream")
    	
    rs.Save stm, adPersistXML  
    	
    Response.ContentType = "text/xml"
    Response.Write stm.ReadText
    
    %> 

    L'oggetto Stream è essenzialmente un file salvato in memoria e non su disco, quindi l'istruzione rs.Save stm, adPersistXML essenzialmente converte (visto che non viene memorizzato da nessuna parte) il recordset in formato XML. Infine il contenuto dello stream (proprietà ReadText dell'oggetto ADO Stream), viene restituito al client in formato XML (Response.ContentType = "text/xml").
    A questo apunto il server ha terminato il suo compito; al client è stata restituita una pagina XML che contiene il recordset (struttura, intestazioni e valori dei campi): ora sta ad ADO convertirla in un oggetto Recordset ADO.

    Vediamo com'è il codice sul client:

    Set rs = New ADODB.Recordset
    With rs
        If .State = adStateOpen Then .Close
        .CursorLocation = adUseClient
        .Open "http://localhost/inetpub/wwwroot/query.asp?TableName=Address"
    End With

    Come vedete si tratta di un codice molto semplice: viene impostato il cursore per recordset, come già detto, su adUseClient e quindi si invoca il metodo Open dell'oggetto Recordset indicando come origine dati l'URL della pagina ASP (in questo caso il nome della tabella da aprire è Address).

    Tutto qui? In effetti, se non fosse per la carenza di documentazione a riguardo, fin qui sarebbe piuttosto semplice; le cose si complicano un po' se qualcosa va storto...
    Infatti se viene generato un errore in una fase qualsiasi, come ad esempio in fase di connessione al database o di lettura del recordset, ADO si aspetta comunque di ricevere un oggetto Recordset, o quantomeno "un qualcosa che ci assomigli" (mi riferisco al recordset formattato in XML) e non un messaggio d'errore. Come fare, dunque? Qui occorre un po' d'astuzia... Io ho risolto con un piccolo artificio: ADO si aspetta un recordset? Un recordset qualunque, poiché non sa che c'è nel database... Bene, in caso di errore genero un recordset di una sola riga con i campi necessari a descrivere il tipo di errore; il client si accorge che il recordset è un po' "particolare" e gestisce l'errore.
    Vediamolo in pratica: sulla pagina ASP che genera il recordset, dopo aver inserito un bel On Error Resume Next all'inizio della procedura (la gestione degli errori in ASP non è il massimo), prima di salvare il recordset scrivo:

    <% 
    
    If Err Then
    	Set rs = Server.CreateObject("ADODB.Recordset")
    	With rs
    		If .State = adStateOpen Then .Close
    		.Fields.Append "Type", adVarChar, 9
    		.Fields.Append "Source", adVarChar, 50
    		.Fields.Append "Description", adVarChar, 255
    		.Fields.Append "Code", adInteger 
    		.Open
    		.AddNew
    		.Fields("Type") = "##Error##"
    		.Fields("Source")= Err.Source
    		.Fields("Description") = Err.Description
    		.Fields("Code") = Err.Number
    		.BatchUpdate
    	End With
    End If
    
    %> 

    In pratica nel caso in cui venga restituito un errore, viene generato un recordset che riporta il codice, la descrizione dell'errore e l'oggetto che l'ha generato; il campo Type viene completato con la stringa "##Error##" affinché il client riconosca che quel recordset non contiene i dati prelevati dalla tabella del database ma la descrizione di un messagio d'errore. La verifica dello stato del recordset e relativa chiusura nel caso in cui risulti aperto non dovebbe esser necessaria, poiché si presume che se è avvenuto un errore, il recordset non sia stato aperto. Una verifica in più, però, non fa mai male: almeno ci togliamo ogni ombra di dubbio.

    Affinché il client si accorga che il recordset restituito dall'ASP contiene un messaggio d'errore anziché i dati voluti, ho effettuato un controllo sul nome ed il valore del primo campo: se risultano essere rispettivamente Type e "##Error##", stampo a video il contenuto degli altri campi per segnalare l'errore:

    If rs.Fields(0).Name = "Type" And rs.Fields(0).Value = "##Error##" Then
        MsgBox rs("Description") & vbCrLf & "Codice: " & _
    	rs("Code"), vbCritical, rs("Source")
    End If

    Ho supposto che un controllo combinato sul nome del campo ed il suo contenuto sia sufficiente per stabilire se si tratta di un recordset contenente un messaggio di errore o i dati voluti: in caso contrario si possono modificare tali valori.

    A questo punto abbiamo il nostro bel recordset (o il nostro bel messaggio d'errore :-); lo modifichiamo in locale restando sconnessi dal database remoto, quindi abbiamo bisogno di salvare le modifiche. Anche in questo caso ci appoggeremo ad una pagina ASP che effettuerà l'update (batch) e anche in questo caso utilizzaremo XML come veicolo per la comunicazione tra il client ed il server, inviando uno stream come parametro alla pagina ASP.
    La pagina ASP che eseguirà l'update è forse ancora più semplice di quella che esegue la query:

    <% 
    
    Set rs = Server.CreateObject("ADODB.Recordset")
    Set stm = Server.CreateObject("ADODB.Stream")
    Set conn = Server.CreateObject("ADODB.Connection")
    	
    conn.ConnectionString = "driver={Microsoft Access Driver (*.mdb)};dbq=" & _
    	Server.MapPath("example.mdb")
    conn.CursorLocation = adUseClient
    conn.Open 
    	
    With rs
    	.CursorLocation = adUseClient
    	.Open Request
    	.ActiveConnection = conn
    	.UpdateBatch
    End With
    
    %> 

    Innanzitutto si imposta la connessione attiva dell'oggetto Recordset (ActiveConnection) al database già aperto in precedenza, dopo di che si richiama il metodo UpdateBatch che aggiorna il recordset. Si noti solo come viene aperto il recordset: viene passato direttamente il codice XML inviato dal client e convertito automaticamente da ADO.

    Da quanto abbiamo appena visto, possiamo già immaginare come dovrà essere la procedura per l'aggiornamento sul client: il recordset viene salvato in uno stream XML, quindi inviato alla pagina ASP. L'unico problema è come inviare al server lo stream XML e come ricevere un eventuale messaggio d'errore: A questo ci pensa un nuovo oggetto introdotto in ADO 2.5: MSXML, un oggetto che fornisce l'interfaccia per la gestione degli XML.
    Il codice sul client sarà il seguente:

    Dim stm As ADODB.Stream
    Dim xml As MSXML.XMLHTTPRequest
    
    Set xml = New MSXML.XMLHTTPRequest
    Set stm = New ADODB.Stream
       
    rs.Save stm, adPersistXML
    xml.Open "POST", "http://localhost/inetpub/wwwroot/Update.asp", False
    xml.Send stm.ReadText
        
    If xml.responseText <>  "" Then MsgBox Right(xml.responseText, _
    	Len(xml.responseText) - InStr(xml.responseText, vbCrLf) - 1), _
    	vbCritical, Left(xml.responseText, InStr(xml.responseText, vbCrLf) - 1)

    Così viene creato lo stream XML contenente la struttura ed i dati del recordset e con i metodi Open quindi Send dell'oggetto MSXML viene prima aperta la pagina ASP, poi inviato l'XML con il metodo POST.
    L'ultima riga introduce la gestione degli errori per questa procedura: l'oggetto MSXML permette anche di ricevere dati in formato XML in risposta da una pagina ASP. Questo grazie alla proprietà responseText: in questo caso vengono stampati a video il codice di errore, la descrizione e l'oggetto che lo ha generato, inviati dalla pagina ASP in questo modo:

    <% 
    
    If Err Then
    	Response.ContentType = "text/xml"
    	Response.Write Err.Source & vbCrLf & Err.Description _
    		& vbCrLf & "Codice: " & Err.Number
    End If
    
    %> 

     

    Implementazioni:

    Come avete potuto constatare, ho considerato solo gli aspetti fondamentali per la realizzazione di questo esempio, al fine di renderne più semplice la comprensione.
    Utili implementazioni possono essere, oltre ad una più dettagliata gestione degli errori (l'importante era capire come intercettarli e processarli), la gestione delle transazioni, l'autenticazione dell'utente e magari anche la generazione della query SQL da parte del client. A tal proposito, però, è necessario prestare la massima attenzione poiché se si passasse la query alla pagina ASP, sarebbe possibile che l'utente generi una query del tipo "DROP TABLE", il che non sarebbe troppo salutare per il nostro db...
    In generale si ricordi che le pagine ASP sono l'interfaccia di gestione del database e che la directory su cui questo risiede ha sia i permessi di lettura che di scrittura. Quindi sarebbe meglio che le pagine ASP non risiedano nella stessa directory del database e progettarle attentamente affinché l'utente non si prenda troppe libertà (come nel caso della query citata poc'anzi).

     

    II metodo - DLL ActiveX

    Come procedere:

    Anche questo progetto, ovviamente, è diviso tra i componenti client e server: Il livello client è un front-end in Visual Basic molto simile a quello sviluppato nell'esempio precedente, mentre sul server sono raggruppati il livello fonte dati (che sarà sempre il nostro database Access) ed il livello intermedio, costituito non più da un'insieme di pagine ASP, ma da una DLL ActiveX.
    Questa DLL esporrà fondamentalmente tre medoti che consentiranno al client di stabilire una connessione con il database, ottenere un oggetto Recordset e inviare le modifiche al recordset modificato.
    Il metodo per la connessione al database è così definito:

    Public Function Connect() As String
        
        Set conn = New ADODB.Connection
        
        On Error GoTo ErrHandler:
        
        With conn
            .CursorLocation = adUseClient
            .ConnectionString = "driver={Microsoft Access Driver (*.mdb)};dbq=" & _
    			App.Path & "\example.mdb"
            .Open
        End With
        
        Exit Function
    
    ErrHandler:
        Connect = Err.Source & vbCrLf & Err.Number & vbCrLf & Err.Description
        
    End Function

    Fino a qui niente di nuovo: viene effettuata la connessione al database allo stesso modo di quanto visto sopra e nel caso in cui venga generato un errore, viene restituita al client una stringa che include il codice d'errore, la descrizione e l'oggetto che lo ha generato.
    Conn è un oggetto ADODB.Connection dichiarato a livello di modulo affinché mantenga la sua validità all'interno di tutti e tre i metodi della classe.

    Il metodo per restituire al client l'oggetto Recordset è invece:

    Public Function GetRecordset(TableName As String) As Recordset
        
        On Error Resume Next
        
        Connect
        
        Set rs = New ADODB.Recordset
        With rs
            .CursorLocation = adUseClient
            .Source = "SELECT * FROM " & TableName
            Set .ActiveConnection = conn
            .CursorType = adOpenStatic
            .LockType = adLockOptimistic
            .Open
        End With
        
        Set GetRecordset = rs
        
    End Function

    Anche questo è molto simile a quanto visto precedentemente: il nome della tabella viene passato come parametro alla funzione ed utilizzato nella generazione della query SQL ed i cursori applicati al recordset sono gli stessi dell'esempio precedente. Come nell'esempio precedente viene ristabilita la connessione al solito database ed impostata come connessione attiva del recordset.
    A differenza dell'esempio precedente, però, qui non è necessaria una gestione degli errori: infatti se avviene un errore nella connessione al database, questo viene gestito dal metodo Connect, mentre se viene generato un errore nella generazione del recordset, dal momento che questo viene restituito direttamente al client, senza alcuna conversione di formato, l'errore potrà benissimo venir gestito dal client stesso. Discorso analogo vale per l'altro metodo, quello che ci permette di aggiornare le modifiche:

    Public Function UpdateRecordset(RSToUpdate As Recordset) As Recordset
         
        On Error Resume Next
        
        Connect
        
        Set rs = RSToUpdate
        Set rs.ActiveConnection = conn
        
        rs.Filter = adFilterPendingRecords
        rs.UpdateBatch adAffectGroup
        
        Set UpdateRecordset = rs
    
    End Function

    Anche questo codice è già visto, ma a differenza di quanto fatto precedentemente, viene impostato un filtro al recordset per isolare solo i record modificati, quindi questi (e solo questi) vengono aggiornati con il metodo UpdateBatch.
    Così come l'oggetto Connection, anche l'oggetto ADODB.Recordser, rs, è dichiarato a livello di modulo.

    Se questo era il componente a livello intermedio (middle-tier), piuttosto simile a quello visto nell'esempio precedente (se non più semplice, visto che adotta un numero minore di tecnologie), il componente a livello di client sarà forse ancora più semplice e forse più efficace, poiché permetterà una migliore gestione degli errori.
    Innazitutto è necesario istanziare gli oggetti:

    Public rs As ADODB.Recordset
    Public ds As RDS.DataSpace
    Public objProxy As Object
    
    Set ds = New RDS.DataSpace
    ds.InternetTimeout = 15000
        
    Set objProxy = ds.CreateObject("DLLRemoteDB.clsRemoteDB", "http://localhost")

    I tre oggetti vengono definiti a livello di modulo, così da mantenere la loro validità all'interno di tutte le procedure; qui è stato introdotto un nuovo tipo di oggetto: RDS.DataSpace. La connessione tra il livello client ed il livello intermedio viene quasi sempre gestita da RDS (mentre nell'esempio precedente è stato utilizzato l'oggetto MSXML) e l'oggetto DataSpace consente appunto di creare un proxy per gestire tale connessione. Per completezza viene impostato un TimeOut di 15 secondi per garantire il completamento della connessione anche nei casi peggiori: mi auguro non dobbiate aumentare tale valore...

    Una volta creato il proxy, si può direttamente richiamare il metodo GetRecordset fornito dal nostro ActiveX, poiché la connessione, se ben vi ricordate, viene impostata automaticamente. Però, dal momento che dobbiamo anche verificare che la connessione si andata a buon fine e l'invio di un eventuale messaggio d'errore è stato delegato unitamente al metodo Connect, conviene prima invocare quest'ultimo, verificare che non siano stati generati errori, quindi richiedere il recordset.

    Dim strErr As String
    
    strErr = objProxy.Connect
        
    If strErr = "" Then
        Set rs = New ADODB.Recordset
        Set rs = objProxy.GetRecordset("Address")
    Else
        MsgBox Right(strErr, Len(strErr) - InStr(strErr, vbCrLf) - 1), vbCritical, Left(strErr, InStr(strErr, vbCrLf) - 1)
    End If

    In questo modo se non vengono generati errori viene invocato il metodo GetRecordset del proxy, pasandogli come parametro il nome della tabella sulla quale eseguire la query, altrimenti viene visualizzato un messaggio con la descrizione dell'errore.

    Una volta apportate le modifiche al recordset, basta richiamare il metodo UpdateRecordset passandogli come parametro il recordset modificato. Come nel caso precedente viene fatta una verifica in fase di ripristino della connessione al database:

    Dim strErr As String
    
    strErr = objProxy.Connect
        
    If strErr = "" Then
        Set rs = objProxy.UpdateRecordset(rs)
    Else
        MsgBox Right(strErr, Len(strErr) - InStr(strErr, vbCrLf) - 1), vbCritical, Left(strErr, InStr(strErr, vbCrLf) - 1)
    End If

     

    Implementazioni:

    Anche in questo esempio ho considerato solo gli aspetti fondamentali, al fine di renderne più semplice la comprensione.
    Analogamente a quanto detto per l'esempio precedente, utili implementazioni possono essere, oltre ad una più dettagliata gestione degli errori (in questo caso non molto diversa da quella cui si è abituati di solito per la gestione di un database locale), la gestione delle transazioni, l'autenticazione dell'utente e magari anche la generazione della query SQL da parte del client, con le stesse note del precedente esempio.

     

    Conclusioni:

    A questo punto può venir spontaneo chiedersi: quale soluzione è preferibil adottare? L'utilizzo di un componente ActiveX o un componente realizzato in ASP?
    Non credo esista una risposta definitiva o, almeno io non sono in grado di darla. Sono due soluzioni diverse, nonostante si prefiggano lo stesso scopo e ottangano lo stesso risultato. La scelta migliore è a mio avviso quella che meglio si adatta alle esigenze specifiche, a seconda delle caratteristiche di ogni singola applicazione. Ad esempio se l'applicazione deve utilizzare XML come formato di interscambio, non solo tra client e server, allora la soluzione più adatta è la prima; la seconda, dal canto suo, permette una migliore e più efficiente gestione degli errori e permette, inoltre, l'implementazione di regole aziendali o di procedure complesse che richiedono una maggior flessibilità rispetto a quella offerta da un linguaggio di scripting; inoltre può risultare più semplice interfacciarsi ad altri componenti COM distribuiti su più livelli. La prima soluzione, ancora, risulta più immediata nel caso di applicazioni Web based, che non richiederebbero così l'utilizzo di componenti compilati, oppure nel caso in cui non si abbia la facoltà di registrare componenti sul server (come ad esempio nel caso di molti servizi di Web Hosting).
    Queste sono solo alcune ipostesi che mi vengono in mente: dal momento che generalmente un'applicazione di questo tipo richiede un analisi preventiva piuttosto attenta e complessa, non risulterà difficile scegliere la soluzione che meglio si adatterà a quella specifica esigenza: l'importante è sapere che non c'è un'unica strada e conoscerne i punti d'accesso ed i possibili sbocchi.
    Spero che questo breve articolo sia stato d'aiuto a tal proposito, che vi abbia permesso di acquisire le nozioni necessarie per costruire lo scheletro di un'applicazione client/server a tre livelli basata su una fonte dati remota. Ora sta a voi implementarla secondo le vostre necessità.

     

    Appendice: struttura di un recordset XML

    Prima di chiudere, vale la pena di dare un'occhiata a come appare l'ADO Stream che contiene il recordset formattato in XML.
    Questo è il risultato della query che ritorna lo stream al client:

    <xml xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'
    	xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882'
    	xmlns:rs='urn:schemas-microsoft-com:rowset'
    	xmlns:z='#RowsetSchema'>
    <s:Schema id='RowsetSchema'>
    	<:ElementType name='row' content='eltOnly' rs:updatable='true'>
    		<s:AttributeType name='Name' rs:number='1' rs:nullable='true'
    			 rs:write='true' rs:basetable='Address' rs:basecolumn='Name'>
    			<:datatype dt:type='string' rs:dbtype='str' dt:maxLength='50'/>
    		</s:AttributeType>
    		<:AtributeType name='Address' rs:number='2' rs:nullable='true'
    			 rs:write='true' rs:basetable='Address' rs:basecolumn='Address'>
    			<:datatype dt:type='string' rs:dbtype='str' dt:maxLength='50'/>
    		<:AttributeType>
    		<s:AttributeType name='City' rs:number='3' rs:nullable='true'
    			 rs:write='true' rs:basetable='Address' rs:basecolumn='City'>
    			<s:datatype dt:type='string' rs:dbtype='str' dt:maxLength='50'/>
    		</s:AttributeType>
    		<s:AttributeType name='Tel' rs:number='4' rs:nullable='true'
    			 rs:write='true' rs:basetable='Address' rs:basecolumn='Tel'>
    			<s:datatype dt:type='string' rs:dbtype='str' dt:maxLength='10'/>
    		</s:AttributeType>
    		<s:extends type='rs:rowbase'/>
    	</s:ElementType>
    </s:Schema>
    <rs:data>
    	<z:row Name='Pippo' Address='Via Battisti, 35' City='Trieste'
    		 Tel='000111'/>
    	<z:row Name='Pluto' Address='Via XX Settembre, 15' City='Genova'
    		 Tel='111000'/>
    	<z:row Name='Paperino' Address='Corso V. Emanuele, 30'
    		 City='Milano' Tel='101010'/>
    </rs:data>
    </xml>

    Invece quello che segue è il contenuto dello stream con le modifiche al recordset che viene passato alla pagina ASP predisposta per l'aggiornamento:

    <xml xmlns:s='uuid:BDC6E3F0-6DA3-11d1-A2A3-00AA00C14882'
    	xmlns:dt='uuid:C2F41010-65B3-11d1-A29F-00AA00C14882'
    	xmlns:rs='urn:schemas-microsoft-com:rowset'
    	xmlns:z='#RowsetSchema'>
    <s:Schema id='RowsetSchema'>
    	<s:ElementType name='row' content='eltOnly' rs:updatable='true'>
    		<s:AttributeType name='Name' rs:number='1' rs:nullable='true'
    			 rs:write='true' rs:basetable='Address' rs:basecolumn='Name'>
    			<s:datatype dt:type='string' rs:dbtype='str' dt:maxLength='50'/>
    		</s:AttributeType>
    		<s:AttributeType name='Address' rs:number='2' rs:nullable='true'
    			 rs:write='true' rs:basetable='Address' rs:basecolumn='Address'>
    			<s:datatype dt:type='string' rs:dbtype='str' dt:maxLength='50'/>
    		</s:AttributeType>
    		<s:AttributeType name='City' rs:number='3' rs:nullable='true'
    			 rs:write='true' rs:basetable='Address' rs:basecolumn='City'>
    			<s:datatype dt:type='string' rs:dbtype='str' dt:maxLength='50'/>
    		</s:AttributeType>
    		<s:AttributeType name='Tel' rs:number='4' rs:nullable='true'
    			 rs:write='true' rs:basetable='Address' rs:basecolumn='Tel'>
    			<s:datatype dt:type='string' rs:dbtype='str' dt:maxLength='10'/>
    		</s:AttributeType>
    		<s:extends type='rs:rowbase'/>
    	</s:ElementType>
    </s:Schema>
    <rs:data>
    	<rs:update>
    		<rs:original>
    			<z:row Name='Pippo' Address='Via Battisti, 35' City='Trieste'
    				 Tel='000111'/>
    		</rs:original>
    		<z:row Address='Via Battisti, 36'/>
    	</rs:update>
    	<z:row Name='Pluto' Address='Via XX Settembre, 15' City='Genova'
    		 Tel='111000'/>
    	<z:row Name='Paperino' Address='Corso V. Emanuele, 30'
    		 City='Milano' Tel='101010'/>
    </rs:data>
    </xml>



    [Scarica il progetto d'esempio] Ultima revisione: 04/04/2001

    © 2001, Sirri Moreno (sirri@morenosoft.com)
    E' vietata qualsiasi riproduzione, anche parziale, senza il consenso dell'autore.