|
Nella fase di providing un
dataset interroga un database (o legge un file) caricando i dati in
memoria; nella fase di resolving invece si salvano le
modifiche indietro nel database. La fase di resolving è più
complessa del providing, e comprende un certo lavoro che viene svolto
“dietro le quinte”. Quando l’utente modifica il
dataset, per esempio tramite una interfaccia utente, il dataset tiene
traccia di tutte le modifiche fatte. Quando causa il salvataggio
delle modifiche (il modo canonico è invocare il metodo
DataSet.saveChanges()), vengono generati i comandi SQL
necessari per riportare i cambiamenti indietro nel database. La cosa
importante da capire è che questo non è sempre
possibile. Vediamo di capire che cosa succede esaminando il caso di
una query SQL. Se costruiamo, per esempio, un QueryDataset con query
SELECT NOME, INDIRIZZO FROM AGENDA, non è detto che il
dataset sia in grado di aggiornare il database. Il problema
principale sono le chiavi primarie. Se modifico il campo NOME di un
record, per aggiornare il database è necessario utilizzare
qualcosa come UPDATE AGENDA SET NOME = ‘nuovonome’
WHERE ID = 123. Notare che ho aggiunto un WHERE ID=123:
una clausola come questa è necessaria per individuare dove
deve avvenire l’aggiornamento. Quando si specifica una
query come proprietà di un dataset, se la query non contiene
abbastanza informazioni per l’aggiornamento, JBuilder tenta di
modificarla per aggiungere abbastanza informazioni da riuscire a
riportare indietro le modifiche nel database: tipicamente aggiunge
nella SELECT un campo chiave primaria, se c’è nella
tabella. Per questo motivo è buona norma avere in ogni tabella
una chiave primaria, e effettuare delle SELECT che includano la
chiave primaria. Se il dataset non riesce a modificare la query
opportunamente (per esempio perché la tabella comprende alcuna
chiave primaria) il DataSet risultante sarà in sola
lettura.
Il processo di risoluzione di una query
può essere personalizzato utilizzando un apposito componente
resolver. I dataset infatti hanno una proprietà resolver
che serve a collegarli ad un componente di questo tipo. I
resolver sono event driven: infatti vengono chiamati i loro
event handler nelle fasi cruciali di risoluzione della query.
Consideriamo il caso di un QueryResolver che gestisce la
risoluzione di una query SQL. Il resolver viene invocato prima e dopo
le operazioni di inserzione, aggiornamento e cancellazione di righe,
e in caso di errore. Facciamo un esempio di uso del resolver;
supponiamo di voler evitare che un utente non autorizzato cancelli
delle righe da un database. Possiamo ottenere questo risultato
semplicemente aggiungendo un resolver, collegandolo al dataset e
gestiendo l’evento deletingRow come segue:
void resolver_deletingRow(
ReadWriteRow readWriteRow,
ResolverResponse resolverResponse
) throws DataSetException {
if(<UTENTE_NON_AUTORIZZATO>)
resolverResponse.skip();
else
resolverResponse.resolve();
}
L’event handler verrà
chiamato prima di eseguire l’operazione, e il resolver
potrà dire al dataset di ignorare l’operazione (skip)
se l’utente non è autorizzato, oppure di eseguirla
(resolve). Un’altra possibile risposta è abort()
per interrompere la risoluzione. I resolver sono indicati oltre
che per intervenire in casi come questo, anche per gestire errori o
eseguire operazioni contemporanee all’aggiornamento del
database.
|