Newsgroup: it.comp.lang.delphi
Titolo: dbExpress e master-details su campi con nomi diversi

Salve,
sto lavorando in Delphi 6 su database Firebird cui accedo
tramite dbExpress ad una gestione anagrafica di articoli,
e mi trovo nella situazione in cui devo mettere in relazione
master-details rispettivamente la tabella ARTICOLI e la
tabella LISTINI mediante i campi CODICE (nella tabella
ARTICOLI) e CODART (nella tabella LISTINI).
L'apertura del Client-Dataset che fa riferimento alla tabella
master (cioè la tabella ARTICOLI) non causa alcun problema.
Quando invece provo ad aprire il Client-Dataset che fa
riferimento alla tabella details (cioè la tabella LISTINI) ottengo
un errore del tipo: "DataSet1: Field 'CODICE' not found"
Tenete conto che nella mia applicazione non è presente alcun
oggetto "DataSet1" in quanto gli oggetti utilizzati sono:
- qbART: TSQLClientDataset (ARTICOLI)
- dsART: TDataSource (ARTICOLI)
- qbLIS: TSQLClientDataset (LISTINI)
Allego altre informazioni sul contenuto delle proprietà
più significative (in questa questione) dei suddetti oggetti:
- qbART.CommandText := 'SELECT * FROM ART';
- qbLIS.CommandText := 'SELECT * FROM ART_LIS';
- qbLIS.MasterSource := dsART;
- qbLIS.MasterFields := 'CODICE';
- qbLIS.IndexFieldNames := 'CODART';
La relazione Master-Details è stata impostata utilizzando il
wizard che si apre quando si clicca nel pulsantino all'interno
della proprietà MasterSource;
Anche quando clicco per la seconda volta (cioè dopo aver impostato
le proprietà per la relazione master-details) nel pulsantino all'interno
della proprietà MasterSource per far aprire il wizard appare l'errore
"DataSet1: Field 'CODICE' not found"
Mi sfugge qualcosa ? Avete suggerimenti ?
Grazie,
Riccardo
--- 

>Mi sfugge qualcosa ? Avete suggerimenti ?
1) Non usare TSQLClientDataset
2) Non usare il wizard per il master/detail
Procedi così:
1) Utilizza dei TSQLDataset, imposta il command type a ctQuery, 
2) Nella query detail metti una condizione WHERE CODART = :CODICE (nota
che il nome del parametro deve essere lo stesso del campo PK nella tabella
master, spero di aver capito come sono fatte le tue tabelle)
3) Collega la query detail alla master con il TDatasource. Delphi cercherà
automaticamente un campo nella query master che ha lo stesso nome del
parametro. Se è diverso va assegnato da codice.
4) Utilizza *un solo* dataset provider e collegalo alla query master
5) Aggiungi due TClientDataset.
6) Collega un client dataset al provider. Questo sarà il master.
7) Fai click con il tasto destro, Fields Editor - Add all fields.
8) Troverai che c'è un campo in più di tipo TDatasetField. Questo campo
speciale contiene un dataset con i detail *per il record corrente*.
9) Nel secondo client dataset, imposta la proprietà DatasetField al campo
dataset del primo client dataset. *Non collegarlo* al provider.
10) Procedi come al solito per agganciare i client dataset ai componenti
dataware.
11) Per salvare le modifiche, fai l'apply del *solo* client dataset
*master*. I detail vengono inviati automaticamente. Idem per il refresh.
In questo modo Delphi passa i record detail direttamente nel datapacket
associati al record master, in entrambe le direzioni.
Il vantaggio è che Delphi legge i record detail solo quando legge il
record master (e se usi IncrementalFetching  a True e PacketRecords ad un
valore appropriato eviti di scaricarti tutti i dati di tutte le tabelle,
ma li leggi di volta in volta).
Mettendo in master Detail i client dataset (come fa TSQLClientDataste, che
è stato deprecato in D7, perché non ha mai funzionato bene) obblighi il
componente a leggersi entrambe le tabelle e fare molto più lavoro lato client.
Luigi D. Sandon - D&amp;D Support Team
--- posted by geoForum on 
http://www.delphiedintorni.it


Ciao,
volevo ringraziarti per la preziosa, chiara, ed interessantissima risposta.
Sto facendo un po' di prove per poter valutare bene il tutto... ma sono
già riuscito a mettere in funzione il meccanismo che mi hai descritto.
Al momento, l'unico neo di questa soluzione mi sembra il moltiplicarsi
in modo esponenziale degli oggetti necessari... infatti al posto di solo
due TSQLClientDataset sono necessari:
- 2 TClientDataset
- 2 TSQLDataset
- 1 TDataSource
- 1 TDataSetProvider
Comunque credo di dover approfondire non poco tutta la questione.
Grazie ancora,
Riccardo
-- 

- 1 TDataSetProvider
Di fatto il TSQLClientDataset non è altro che un dataset + provider +
clientdataset riuniti in un unico componente per comodità. Il problema è
che non è stato fatto bene (e alla fine, in D7, deprecato) e comunque si
perdono delle funzionalità disponibili utilizzando il "puzzle" di cui sopra.
Il modello di dbExpress è quello: dataset unidirezionali che richiedono un
provider + clientdataset per il buffer lato client. Almeno la possibilità
di fare il "nesting" dei detail ti risparmiano di dover utilizzare un
secondo provider, e migliora le prestazioni.


 secondo provider, e migliora le prestazioni.
Approfitto della tua disponibilità chiedendoti come è possibile
usare i campi di aggregazione con la soluzione da te proposta.
Supponiamo che io volessi usare un campo di aggregazione che mi
calcoli il prezzo medio di un articolo (master) in base ai vari prezzi
di listino (details).
E' una cosa che ho già fatto con TSQLClientDataset ma senza usare
Provider e ClientdDataset... con Provider e ClientDataset infatti non
mi sta riuscendo... in verità devo ancora fare delle prove più
approfondite ma un suggerimento mi sarebbe di grande aiuto.
Grazie ancora,
Riccardo
-- 

 approfondite ma un suggerimento mi sarebbe di grande aiuto.
Devo forse crearlo sul ClientDataset del Details ??!!
Hmm... credo proprio di sì... andrò a verificare.

>Devo forse crearlo sul ClientDataset del Details ??!!
Sě, perché i prezzi da mediare li hai nei dettagli, non nel master.
A meno che non lo calcoli direttamente con una query sul DB, e ti fai
passare il prezzo medio come un campo del master. Ma questo probabilmente
rende complessa e poco performante la query.
Forse potresti creare una terza query (i campi me li sto inventando)
SELECT AVG(PREZZO) 
FROM LISTINO 
WHERE COD_ART = :COD_MASTER
GROUP BY COD_ART
che dato un codice master calcola la media dei prezzi di listino per quel
codice. Quasta la puoi collegare come fosse un secondo "detail" e passarlo
al solito modo.


 al solito modo.
Bene, anche io avevo pensato a questa soluzione ma per risolvere un altro
problema,
e cioè il recupero di informazioni che non cambieranno mai con l'interazione
tra utente
e applicazione... mi spiego meglio:
A) ci sono delle informazioni ricavabili in SQL che però potrebbero variare
non appena
l'utente effettua delle modifiche lato client (vedi modifica dei prezzi di
listino e dunque
anche del prezzo medio),
B) mentre ci sono altre informazioni che rimangono invariate o meglio non
sensibili alle
modifiche eseguite dall'utente (vedi costo ultimo di acquisto di un prodotto
ricavabile
da un'altra tabella a cui non si accede dalla mia gestione anagrafica e che
dunque non
può essere modificata dall'utente che sta utilizzando la gestione
anagrafica).
Grazie comunque per i sempre graditi suggerimenti
Riccardo.
-- 

 secondo provider, e migliora le prestazioni.
Piccolo problema. La mia tabella listini ha più o meno qeusta struttura:
- CODART (Codice articolo)
- CODLIS (Codice listino)
- PREZZO (Prezzo di listino)
La chiave primaria della tabella listini è costituita dai due campi CODART e
CODLIS,
in quanto per ovvi motivi non deve essere possibile inserire per lo stesso
articolo due
prezzi diversi per lo stesso listino... insomma univocità (CODART,CODLIS).
Utilizzando il meccanismo dei "nesting details" ho un inconveniente e cioè
che
mi accorgo delle eventuali violazioni di chiavi primarie (o di indici
univoci)
soltanto dopo avere eseguito il metodo ApplyUpdates del
Master-ClientDataset.
Mi piacerebbe (anzi direi che sarebbe molto importante magari senza dover
scrivere esplicitamente troppe righe di codice ogni volta che mi si presenta
tale esigenza, di seguito espressa) fare in modo di poter visualizzare un
messaggio che ricordi all'utente l'impossibilità per uno stesso articolo
di inserire due volte lo stesso listino, subito prima del Post di tale
record di
detail "prezzo di listino dell'articolo".
Come fare per dire a Delphi di mantenere tali vincoli (già presenti sul DB
server)
anche lato client (ovvero nel ClientDataset corrispondente ?)
Grazie anticipatamente.
Riccardo
-- 

>Suggerimenti ?
C'è la proprietà Constraint del TDatasetProvider che permette di passare i
metadati dei constraint dal server al client. Il problema è che questa
proprietà si basava sul Data Dictionary che Delphi poteva creare via SQL
Explorer, e non so se con dbExpress c'è modo di utilizzarla.
Controlla che nel dataset la proprietà "GetMetadata" sia True. Vedi anche
se  la proprietà Constraint del TClientDataset ti è d'aiuto, ma rischi sì
di dover duplicare i constraint lato client se Datasnap non è in grado di
propagarli da sé. 
Uno dei problemi di Datasnap/Midas è che Borland non le ha mai dato
veramente il "tocco finale", ma ha lasciato qualche lato un po' ruvido,
troppo occupata ad implementare le ultime "buzzword" con le risorse a
disposizione.
È veramente un peccato perché è una tecnologia potente (non per caso il
remoting di 
ADO.NET
 l'ha copiata, e Borland l'ha introdotta con Delphi 3)
e non obbliga a quel totale cambio di paradigma tipico degli OPF come Bold
e ECO.


 e ECO.
Non ho la più pallida idea di cosa siano gli OPF: Bold e ECO.
:(
Ciao,
Riccardo
--

Non ho la più pallida idea di cosa siano gli OPF: Bold e ECO.
Sono dei framework per la persistenza di oggetti (Object Persistence
Framework). In pratica permettono di memorizzare istanze di una classe
direttamente nel DB, occupandosi della generazione degli appropriati SQL.
Non si parte più progettando il DB nel classico stile relazionale, ma si
parte progettando le classi che implementano il sistema. Il framework
provvede poi a creare le tabelle necessarie e a mappare le classi sulle
tabelle, ma a livello di programma si lavora solo sulle classi, con un
paradigma piuttosto diverso dalla solita programmazione DB. Allo stato
attuale sono molto comodi in alcuni contesti, meno in altri.
