Nel costruire il mio file di utility per accedere al database coi miei dati (SQLite3), ho inserito nel mio primo programma alcune funzioni come
function OpenDbQuery1F1(sql: String): Boolean;
function OpenDbQuery2F1(sql: String): Boolean;
Ciò, perchè mentre è ancora attiva la Query1F1, mi è successo di dovere eseguire una Query2F1. Bene
Sto ora scrivendo un altro programma che accede sempre allo stesso database, ma soltanto attraverso la Query1F1, quindi non ha bisogno di un secondo oggetto Query.
Ebbene, la compilazione di quest'ultimo programma mi restituisce un mesaaggio d'errore perchè non trova la Query2F1.
Francamente non me l'aspettavo. Non capisco perchè, se il file di utility contiene due function di tipo Query, ma al programma corrente occorre richiamarne una sola, il compilatore debba protestare.
Io non so come fate voi di fronte ad una situazione del genere.
Io attualmente vedo solo una via: non utilizzare file utility, ma scrivere le function direttamente dentro ciascun programma.
Mi sembra una scelta bambinesca, ma non saprei fare diversamente.
Per completezza riporto le immagini dei form dei due diversi programmi.
Non hai importato nella Uses il corretto file delle definizioni.
Inoltre hai definito anonime le funzioni sotto elencate.
Riporto qui le uses dei due programmi, perchè non ho capito dove avrei sbagliato.
1-Progetto "DomusRatioCancResta"
unit frm1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, DB, Forms, Controls, Graphics, Dialogs, StdCtrls,
ZConnection, ZDataset,
utilmie, utilmiedb;
type
{ TForm1 }
TForm1 = class(TForm)
BPulsAvanti: TButton;
Button2: TButton;
DataSource1: TDataSource;
Label1: TLabel;
LBggContab: TListBox;
ZConnection: TZConnection;
ZQuery1: TZQuery;
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure LBggContabEnter(Sender: TObject);
private
public
end;
var
Form1: TForm1;
dbCopia, dbCorr, dbOrig, dbProve, percorso: String;
implementation
{$R *.lfm}
{ TForm1 }
var
totRec: Integer;
// dataOggi, dbCopia, dbCorr, dbOrig, dbProve, percorso, sql, status ,striMia: String;
dataOggi, sql, status ,striMia: String;
procedure TForm1.FormCreate(Sender: TObject);
begin
Form1.Caption:= 'CANCELLAZIONE, in Tb.restacassagg di tutti i record di una giornata contabile';
percorso := ExtractfilePath(Application.ExeName);
//------------------------------------ Assegno percorso per l'accesso al DB corretto
dbCopia:= '/media/dirdati/dativari/contabfam/ContabFamdb(copia)'; // )
dbOrig:= '/media/dirdati/dativari/contabfam/ContabFamdb'; // ( per impostare dbCorr:= dbOrig o dbProve -> Form1.Panel1Enter
dbProve:= '/media/dirdati/dativari/contabfam/ContabFamdb_prove'; // )
//------------------------------------------------------------------------------------
end;
2-Progetto DomusRatio (Di questo riporto soltanto la parte iniziale della unit frm1
unit FrmMain;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, DB, Forms, Controls, Graphics, Dialogs, Menus, ExtCtrls,
FileUtil, StdCtrls, ZConnection, ZDataset, Frm5, Frm6, Frm7,
utilmie, utilmiedb;
// FileUtil, process, StdCtrls, ZConnection, ZDataset, Frm5, Frm6, Frm7,
type
{ TForm1 }
TForm1 = class(TForm)
DataSource1: TDataSource;
DataSource2: TDataSource;
Image1: TImage;
LAttesa: TLabel;
MainMenu1: TMainMenu;
Menu1: TMenuItem;
Menu11: TMenuItem;
Menu2: TMenuItem;
Menu21: TMenuItem;
Menu22: TMenuItem;
Menu221: TMenuItem;
Menu222: TMenuItem;
Menu2221: TMenuItem;
Menu2222: TMenuItem;
Menu2223: TMenuItem;
Menu2224: TMenuItem;
Menu2225: TMenuItem;
Menu223: TMenuItem;
Menu224: TMenuItem;
Panel1: TPanel;
ZConnection: TZConnection;
ZReadOnlyQuery1: TZReadOnlyQuery;
ZReadOnlyQuery2: TZReadOnlyQuery;
procedure FormCreate(Sender: TObject);
procedure Menu11Click(Sender: TObject);
procedure Menu21Click(Sender: TObject); // Inserimento Movimenti giornalieri
procedure Menu221Click(Sender: TObject); // Ricerca Movimenti per Descrizione (tipoRicerca:= '0')
procedure Menu2221Click(Sender: TObject); // Ricerca Movimenti per Voce principale di Cassa (tipoRicerca:= '1')
procedure Menu2222Click(Sender: TObject); // Ricerca Movimenti per Voce di Sottoconto di Cassa (tipoRicerca:= '2')
procedure Menu2223Click(Sender: TObject); // Ricerca Movimenti per Voce Primaria di Collegamento (tipoRicerca:= '3')
procedure Menu2224Click(Sender: TObject); // Ricerca Movimenti per Voce Secondaria di Collegamento (tipoRicerca:= '4')
procedure Menu2225Click(Sender: TObject); // Ricerca Movimenti per Voce di Sottoconto di Collegamento (tipoRicerca:= '5')
procedure Menu223Click(Sender: TObject); // Ricerca per Componente familiare (tipoRicerca:= '6')
procedure Menu224Click(Sender: TObject); // Ricerca per Importo (tipoRicerca:= '7')
procedure Panel1Enter(Sender: TObject);
end;
const
maskLire: String = '#,###,##0';
maskEuro: String = '#,###,##0.#0';
var
Form1: TForm1;
dbCopia: String;
dbOrig: String;
dbProve: String;
dbCorr: String;
dataCont, dataSys, nomeGiorno, nomeMese, percorso: String;
ggApertaChiusa: String; // (campo vuoto) = Giornata contabile NUOVA, "C" = Giornata contabile CHIUSA, "A" = Giornata contabile APERTA
tipoValuta: String;
implementation
{$R *.lfm}
{ TForm1 }
procedure TForm1.Menu11Click(Sender: TObject); // Scelta Menu' "File + Esci"
begin
Application.Terminate;
end;
I file delle mie utility sono dentro un'unica directory:
$ ls /media/dirdati/dativari/lazarus_progetti/lazarus_progetti_miei/util_mie
backup daticomuni.pas lib utilmiedb.pas utilmie.pas
riporto parte del contenuto del file "utilmiedb.pas"
unit utilmiedb;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Dialogs,
// DB, Forms, Controls, Graphics, Menus, ExtCtrls,
DB, Forms, Controls, Menus, ExtCtrls,
FileUtil, StdCtrls,
ZConnection, ZDataset;
type
TstrOut = Integer;
function DbContaRecInOpen(sql: String): Integer; // Esegue la OPen del DB tramit Query contenuta in "sql" ed accerta la presenza di record di riscontro
function EstraiVocePianCont(sql: String): String; // Estrae dalla Tabella "piancont" la Voce contabile, a cui accoda il contenuto del campo "ContrPartSiNo"
function EstraiCoVoColleg(sql, segnal: String): String; // Estrae dalla Tabella "racodvoci" il codice di sottoconto contrappoto al tipo inmdicato in "segnal"
function OpenDbQuery1F1(sql: String): Boolean;
function OpenDbQuery2F1(sql: String): Boolean;
implementation
uses
frm1;
function OpenDbQuery1F1(sql: String): Boolean; // funzione usata per accedere alla lettura di un solo Record della TB.piancont o dalla TB.racodvoci
var
swOpenErro: Boolean = False;
begin
//----------- Open del DB ------------------------------------------------------
Form1.ZConnection.Database := dbCorr;
Form1.ZQuery1.SQL.Text := sql;
try
Form1.ZQuery1.Open;
except
on E: Exception do
begin
WriteLn('ERRORE nella OPEN del ContabFamdb (sql: "' + sql + '" ' + IntToStr(E.HelpContext) + ': ' + E.Message);
swOpenErro:= True;
end;
end;
Result:= swOpenErro;
end;
function OpenDbQuery2F1(sql: String): Boolean; // funzione usata per accedere alla lettura di un solo Record della TB.piancont o dalla TB.racodvoci
var
swOpenErro: Boolean = False;
begin
//----------- Open del DB ------------------------------------------------------
Form1.ZConnection.Database := dbCorr;
Form1.ZReadOnlyQuery2.SQL.Text := sql;
try
Form1.ZReadOnlyQuery2.Open;
except
on E: Exception do
begin
WriteLn('ERRORE nella OPEN del ContabFamdb (sql: "' + sql + '" ' + IntToStr(E.HelpContext) + ': ' + E.Message);
swOpenErro:= True;
end;
end;
Result:= swOpenErro;
end;
Spero che possa bastare per aiutarmi a capire come dovrei intervenire.
Le dichiarazioni dovrebbero essere a posto. Non posti però la parte di codice dove usi la funzione (con le Uses).
Se nel codice dove usi la funzione non genera errore e genera solo l'errore per la "mancanza" dell'altra che però non usi, allora è un bel dilemma.
Cerco di riportare il codice con ordine
Il programma richiamala la funzione "DbContaRecInOpen" , presente nella unit "utilmiedb" delle utility mie:
unit frm1;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, DB, Forms, Controls, Graphics, Dialogs, StdCtrls,
FileUtil, ZConnection, ZDataset,
utilmie, utilmiedb;
type
{ TForm1 }
TForm1 = class(TForm)
BPulsAvanti: TButton;
Button2: TButton;
DataSource1: TDataSource;
Label1: TLabel;
LBggContab: TListBox;
ZConnection: TZConnection;
ZQuery1: TZQuery;
procedure Button2Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure LBggContabEnter(Sender: TObject);
private
public
end;
. . .
procedure TForm1.LBggContabEnter(Sender: TObject);
var
dtGGfa: String;
begin
BPulsAvanti.Caption:='Seleziona data per Cancellazione Record e' + LineEnding + Space(30) + 'CLICCA QUI';
dataOggi:= CarDtCalendario();
dtGGfa:= CalcDataGGfa(30);
dbCorr:= dbProve;
CopyFile(dbOrig, dbCorr);
ShowMessage('----- DomusRatioCancResta -----' + System.lineending + 'DB Origine: ' + dbOrig + System.lineending + 'copiato in ' + System.lineending + 'DB di prova: ' + dbProve);
//----------- Open del DB ------------------------------------------------------
ZConnection.Database:= dbCorr;
sql:= 'SELECT DtCoMovg, StaDtMovg FROM riepmovg WHERE DtCoMovg <= ' + dataOggi + ' AND DtCoMovg >= ' + dtGGfa + ' ORDER BY DtCoMovg DESC';
totRec:= DbContaRecInOpen(sql);
l'unit utilmiedb contiene la function DbContaRecInOpen:
unit utilmiedb;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Dialogs,
// DB, Forms, Controls, Graphics, Menus, ExtCtrls,
DB, Forms, Controls, Menus, ExtCtrls,
FileUtil, StdCtrls,
ZConnection, ZDataset;
type
TstrOut = Integer;
function DbContaRecInOpen(sql: String): Integer; // Esegue la OPen del DB tramit Query contenuta in "sql" ed accerta la presenza di record di riscontro
function EstraiVocePianCont(sql: String): String; // Estrae dalla Tabella "piancont" la Voce contabile, a cui accoda il contenuto del campo "ContrPartSiNo"
function EstraiCoVoColleg(sql, segnal: String): String; // Estrae dalla Tabella "racodvoci" il codice di sottoconto contrappoto al tipo inmdicato in "segnal"
function OpenDbQuery1F1(sql: String): Boolean;
function OpenDbQuery2F1(sql: String): Boolean;
implementation
uses
frm1;
function DbContaRecInOpen(sql: String): Integer; // Esegue la OPen del DB tramite Query contenuta in "sql" ed accerta la presenza di record di riscontro
var
totRec: Integer;
begin
Form1.ZConnection.Database:= dbCorr;
Form1.ZQuery1.SQL.Text:= sql;
try
Form1.ZQuery1.Open;
except
on E: Exception do
WriteLn('ERRORE nella OPEN-sql= "' + sql + ' -"' + IntToStr(E.HelpContext) + ': ' + E.Message + '"-');
end;
totRec:= Form1.ZQuery1.RecordCount;
Result:= totRec;
Form1.ZQuery1.Close;
end;
che è quella che viene richiamata.
Tuttavia nella unit sono presenti anche altre function, non richiamate nel programma corrente.
Ebbene il compilatore fornisce il seguente errore per ben 9 volte puntando a tutte le righe tipicamente anomale delle altre function, pur non essendo mai richiamate:
Compila il progetto, Destinazione: DomusRatioCancResta: Codice di uscita 1, Errori: 9
utilmiedb.pas(54,9) Error: identifier idents no member "ZReadOnlyQuery2"
. . .
utilmiedb.pas(138,9) Error: identifier idents no member "ZReadOnlyQuery2"
Io vedo solo 2 alternative:
- creare una unit di utilility per ciascuna function:
- abbandonare il concetto unit di utility e riportare le function di volta in volta che servono, dentro il programma chiamante.
Ti posto intanto un esempio di runtime come lo pensavo io, forse più accessibile.
P.S.: è stato creato in Windows x64, c'è anche la dll di SQLite3. Adattalo eventualmente se usi Linux (in realtà non dovrebbero esserci modifiche se non nelle proprietà di compilazione).
Ti ringrazio per la disponibilità e la pazienza. In effetti io cercavo qualcosa del genere sin da quando ho cominciato a provare l'accesso a database in Lazarus.
Ora che ho preso confidenza con gli strumenti grafici di Zeos, ricominciare praticamente da zero non è semplice, ma sono sicuro che ne vale la pena.
Ho dato una guardata veloce al progettino d'esempio che mi hai gentilmente girato. Ho visto che il programma principale richiama una unity al cui interno ci sono tre utility, di cui la prima è un costruttore, la seconda è un distruttore e l'ultima è la function vera e propria.
Nel costruttore ho incontrato:
ZConnection1 := TZConnection.Create(nil);
. . .
ZTable1 := tZTable.Create(nil);
Non ho capito se quel (nil) è un'impostazione obbligata o se, invece, può essere sostituita da nomi di tabelle(nella prima create) o da nomi di colonne (nella seconda create), come si potrebbe fare, per esempio in fase di generazione di un nuovo database, con le relative tabelle.
Poi credo che l'utilizzo segua il criterio di Zeoslib, cioè, se esiste una connessione già attiva ed occorre procedere con una seconda query, senza chiudere quella attiva, occorre dare vita ad una ZConnection2 dentro un'ulteriore unit, con un suo costruttore, un suo distruttore e la sua funzione di lettura, scrittura, aggiornamento, . . . del database.
Spero di avere capito bene.
Il parametro del costruttore Create generalmente viene usato per consentire l'apparentamento, ossia principalmente chi ha la responsabilità della distruzione del componente.
Nel caso sia nil, qualcuno dovrà chiamare il metodo free del componente.
In questo caso i due componenti che hai indicato vengono messi a FREE dal metodo DESTRUCTOR della classe.
Ora se osservi bene, vedrai che il codice nella parte visuale (unit1) costruisce la classe con il parametro a SELF, questo fà si non sia necessario chiamare il distruttore FREE manualmente in quanto vi provvede automaticamente la FORM1 (self in questo caso indica la FORM1).
MiaUtilita := TMyUtility.Create(self);
Così facendo il metodo DESTRUCTOR dell'istanza "MiaUtilita" verrà chiamato quando la FORM1 si chiude.
Per quello che riguarda la connessione, dentro la classe puoi fare quello che vuoi, inserire 10 ZQuery, 15 ZTable, 5 ZConnection, etc .... (sono numeri a caso chiaramente), e gestire ogni singolo componente o sua proprietà come meglio credi. Sei in una classe e tutti gli "oggetti" della classe appartengono alla classe e li dentro puoi fare ciò che vuoi.
Tra l'altro vedrai che ZConnetcion1 e ZTable1 sono dichiarate "public" e quindi puoi accedervi anche dalla Unit1. In realtà dovrebbero essere dichiarate "private" perchè dovresti accedervi solo con i metodi della classe "TMyUtility".
type
TMyUtility = Class
private
ZConnection1: TZConnection;
ZTable1: TZTable;
public
function LeggiLaPrimaRiga: boolean;
constructor Create(Owner: TComponent); overload;
destructor Destroy; override;
end;
Se vuoi continuare con le componenti grafiche, invece di usare un FORM usa un TDATAMODULE come ti ho indicato in un post precedente.
Magari poi "mixare" intanto le due tecniche finchè non prendi mano.
Ciao
@ DragoRosso
Mentre ti comunico che, per terminare al più presto il programmino che avevo in corso d'opera, ho preferito mettere un pò da parte la novità che mi hai indicato per l'accesso ai DB,
Stamani ho finito di provarlo. Funziona benissimo.
Ora, però, visto che ho la calma necessaria per riconsiderare e modificare quanto fatto sui DB, mi pare il momento giusto per addentrarmi nella tecnica d'accesso RunTime.
Ho perciò ripreso l'esempio che mi hai postato ed ho cercato di applicarne il metodo nel programma appena finito che è il più semplice fra quelli realizzati.
Ho quindi cominciato a formare una Unit Class dove ho riportato il costruttoire ed il distruttore di una ZConection che, penso, dovrebbe rendersi disponibile alle varie occorrenze. Andrebbe perciò personalizzata, al momento, nel costruttore.
Riprendo perciò le righe dell'esempio:
with ZConnection1 do
begin
ControlsCodePage := cCP_UTF8;
AutoEncodeStrings := True;
ClientCodepage := 'UTF-8';
Properties.Add('AutoEncodeStrings=True');
Properties.Add('controls_cp=CP_UTF8');
Properties.Add('codepage=UTF-8');
Port := 0;
Database := '.\db.s3db'; <== qui dovrei mettere il percorso completo del mio DB (dbProve, dbOrig)
Protocol := 'sqlite-3';
end;
Dovrei potere impostare la proprietà Database col percorso del DB da manipolare al momento:
- (dbOrig:= '/media/dirdati/dativari/contabfam/ContabFamdb';)
- (dbProve:= '/media/dirdati/dativari/contabfam/ContabFamdb_prove').
Ecco, non ho capito come intervenire, perchè è il programma chiamante che conosce il percorso corretto, Ma come faccio a passargli la stringa? Qui?
MiaUtilita := TMyUtility.Create(self, dbOrig)
Lo stesso problema nasce quando, subito dopo dovrei impostare i parametri di identificazione della tabella subordinata del DB:
ZConnection1.Connected:= True;
ZTable1 := tZTable.Create(nil);
with ZTable1 do
begin
Connection := ZConnection1;
SortedFields := 'ID';
TableName := 'utenti';
IndexFieldNames := 'ID Asc';
end;
perchè può NON essere sempre la stessa.
Mi dispiace chiedere, ma purtroppo non riesco ad andare avanti da solo.
Nel costruttore della tua classe (TMyUtility) puoi fare solo qualcosa, ad esempio solo istanziare (ossia creare) la connessione senza definire il database ne attivare la connessione stessa. Idem per la ZTable.
Poi puoi definire una funzione dove vai a passare i parametri che preferisci per impostare il database e la connessione.
Ecco, non ho capito come intervenire, perchè è il programma chiamante che conosce il percorso corretto, Ma come faccio a passargli la stringa? Qui?
MiaUtilita := TMyUtility.Create(self, dbOrig)
Come ti accennavo puoi creare nuove funzioni in cui passi ciò che vuoi, espandere come hai indicato il costruttore, etc ...
La progettazione di una classe dipende da cosa devi fare e può richiedere anche giorni o settimane per arrivare ad una versione sufficientemente funzionale.
Devi avere ben chiaro:
1) Scopo della classe: esempio generalizza e isola dal codice l'accesso al database... pensa se domani usassi MySql oppure usi qualcosa di diverso da ZEOS, dovresti solo cambiare o meglio implementare la classe e ricompilare senza cambiare una virgola al tua programma.
2) Componenti della classe (chiamati membri): devi prima di tutto definire quali componenti devi avere nella classe (ad esempio quante ZConnection ? quante ZQuery ?). Ovviamente ci dovranno essere anche tutte le variabili di supporto e di stato.
3) Anche se in un primo momento puoi esporre i componenti come "public", magari per facilitare l'accesso al codice vecchia maniera, poi dovrai pian piano definire le proprietà che ti consentano di interagire con le tue Query e le tue Tabelle, proprietà delle classe senza accedere direttamente ai membri interni.
4) Lo stato della classe deve essere sempre congruo, quindi dovrai provvedere a fare si che se usi il multithreading ci siano delle barriere di protezione (per ora è meglio che questo punto lo "appunti" e lo dimentichi).
Hai già sviluppato un applicativo, creare una classe funzionale è il prossimo esercizio, con tanto di procedure funzioni e proprietà.
Giusto per farti capire (il codice è indicativo e non completo):
type
TMyUtility = Class
private
ZConnection1: TZConnection;
ZTable1: TZTable;
public
property ElencoDatabase: TStringList read getElencoDatabase write setElencoDatabase;
function LeggiLaPrimaRiga: boolean;
constructor Create(Owner: TComponent); overload;
destructor Destroy; override;
end;
C'è una nuova proprietà (che deve essere implementata con una funzione e una procedura "private" ovviamente) che definisce ad esempio il passaggio sia in scrittura che in lettura di quel famoso elenco di nomi dei database. Come vedi è una proprietà e chi la chiama non sà cosa c'è dopo, sà solo che puo passare un elenco di stringhe o ricevere un elenco di stringhe. Sarà la classe a definire come usare questo elenco (sia in ingresso che in uscita).
Per ora basta così ... alla prossima ti chiuderò la proprietà come esempio.
Ciao
2) Componenti della classe (chiamati membri): devi prima di tutto definire quali componenti devi avere nella classe (ad esempio quante ZConnection ? quante ZQuery ?). Ovviamente ci dovranno essere anche tutte le variabili di supporto e di stato.
Allo stato attuale, tali componenti non mi sono noti, perchè inizialmente sono limitati, ma potranno crescere con le conoscenze e con l'uso che ne deriva dalle necessità legate ai prossimi programmi.
quote author=DragoRosso]
C'è una nuova proprietà (che deve essere implementata con una funzione e una procedura "private" ovviamente) che definisce ad esempio il passaggio sia in scrittura che in lettura di quel famoso elenco di nomi dei database. Come vedi è una proprietà e chi la chiama non sà cosa c'è dopo, sà solo che puo passare un elenco di stringhe o ricevere un elenco di stringhe. Sarà la classe a definire come usare questo elenco (sia in ingresso che in uscita).
property ElencoDatabase: TStringList read getElencoDatabase write setElencoDatabase;
[/quote]
Per ora non capisco quale sia la sua utilità, perchè m'immagino che una lettura o scrittura su una tabella di DB (sia fisica che logica) si faccia sempre attraverso il trasferimento di una stringa, in formato sql, nella proprieta ZQuery1.SQL.Text, anche se fino ad ora non ho incontrato niente del genere.
Mi pare che tu non mi stia dando un aiuto, ma tutt'altro.
Non voglio tediarti ulteriormente. Ho capito che Il percorso è lungo ed impegnativo e non mi pare corretto approfittare così tanto della tua disponibilità.
Grazie!
@ DragoRosso
in questo post (http://www.lazaruspascal.it/index.php?topic=2661.msg17493#msg17493) ho trovato uno spunto di partenza per accedere al mio databas SQLite3.
Dopo avere letto una vecchia guida borland (http://www.thebat.altervista.org/sw/Manuali/Borland%20-%20Guida%20alla%20programmazione%20di%20Delphi%207.pdf), ho creasto un modulo dati.
Come prima operazione vorrei eseguire la Open del database, però quella guida non spende granchè sul da farsi relativamente ai database. Non ho trovato altro.
Comunque, con la creazione del suddetto modulo, è stata aggiunta una nuova unit (unit1):
unit Unit1;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils;
type
{ TDataModule1 }
TDataModule1 = class(TDataModule)
procedure DataModuleCreate(Sender: TObject);
private
public
end;
var
DataModule1: TDataModule1;
implementation
{$R *.lfm}
{ TDataModule1 }
procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
end;
end.
Ora dovrei scriverci dentro le istruzioni per aprire il database, sfruttando gli strumenti Zeoslib.
Ora dovrei scriverci dentro le istruzioni per aprire il database, sfruttando gli strumenti Zeoslib.
unit Unit1;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, ZCompatibility,
ZConnection, ZDataset, SQLite3Conn, SQLDB, DB;
type
{ TDataModule1 }
TDataModule1 = class(TDataModule)
procedure DataModuleCreate(Sender: TObject);
private
ZConnection1: TZConnection;
public
end;
var
DataModule1: TDataModule1;
implementation
{$R *.lfm}
{ TDataModule1 }
procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
ZConnection1 := TZConnection.Create(nil);
with ZConnection1 do
begin
ControlsCodePage := cCP_UTF8;
AutoEncodeStrings := True;
ClientCodepage := 'UTF-8';
Properties.Add('AutoEncodeStrings=True');
Properties.Add('controls_cp=CP_UTF8');
Properties.Add('codepage=UTF-8');
Port := 0;
Database := '.\db.s3db';
Protocol := 'sqlite-3';
end;
ZConnection1.Connected:= True;
end;
end.
@ DragoRosso
Grazie per l'instradamento.
Leggendo il codice che hai preparato per me, trovo:
unit Unit1;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, ZCompatibility, // ZCompatibility per la prima volta
ZConnection, ZDataset, SQLite3Conn, SQLDB, DB; // SQLite3Conn, SQLDB per la prima volta
Per capire, comincio da qui, perchè alcune classi richiamate in uses non le mai incontrate prima d'ora, usando i componenti grafici di Zeoslib (ZConnection, Zquery, DataSource). Ritengo che siano necessari nella gestione di DB, tramite codice.
La procedura
procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
ZConnection1 := TZConnection.Create(nil);
with ZConnection1 do
begin
ControlsCodePage := cCP_UTF8; //--- come prima <---------|
AutoEncodeStrings := True; //--- come prima <------| |
ClientCodepage := 'UTF-8'; //--- prima non me ne preopccupavo | | <-----|
Properties.Add('AutoEncodeStrings=True'); //--- sembra la ripetizione di --| | |
Properties.Add('controls_cp=CP_UTF8'); //--- sembra la ripetizione di --| |
Properties.Add('codepage=UTF-8'); //--- sembra la ripetizione di --|
Port := 0; //--- come prima
Database := FrmMain.dbCorr; //--- caricando così il percorso corretto, col DB effettivo o quello di prova
Protocol := 'sqlite-3'; //--- come prima
end;
ZConnection1.Connected:= True;
end;
è il costruttore per la connessione al DB?
Bene, la procedura appena creata nel 1° modulo dati permette di eseguire la Open del database, ma nel momento in cui dovessi andare a leggere o scrivere, occorre creare un altro modulo dati? e così per ogni nuova operazione sul DB?
Come vedi, ogni passo, ho sempre nuove domande da fare. :-\ :-[
@ DragoRosso
Ho guardato la demo, cercando di capire come è stato usato il modulo dati.
Intanto non ho trovato più il costruttore che m'aspettavo dopo l'esercizio precedente.
Ho visto poi che nella finestra del Modulo dati hai inserito tutti gli oggetti TZConnection, TZQuery e DataSource che avevo capito non andavano più impiegati nella tecnica digestione DB a runtime.
Sono rimasto abbastanza sorpreso, perchè così gli strumenti grafici ci sono sempre, sono inseriti solo una volta, è vero, non sono dentro una classe di tipo Form e sono accessibili, tramite uses, da tutte le Form del progetto.
Provo a modificare il mio programma e ti faccio sapere.
Ho notato però che la
procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
ZConnection1.Connected:= True;
ZTable1.Active:= True;
ZReadOnlyQuery1.Active:= true;
end;
imposta a True sia la TZConnection che le TZQuery. Ma così, se non ho capito male, il database è aperto sin dal momento del create del Modulo-Dati, con le Query già pronte a fornire i dati.
E a proposito, nella modifica che ho cominciato, ho visto la il modulo-dati veniva creato automaticamente insieme alla Form1. Io vorrei avviarne il create, al momento del bisogno, dentro la Form1, in modo da potere assegnargli il percorso corretto del database da collegare all'oggetto TZConnection.
Togli le tre righe da DataModuleCreate e inserscile in una nuova procedura che chiamerai quando ne hai bisogno.
La procedura la creerai nel TDataModule sotto "public".
Non conoscendo l'effetto della creazione automatica del modulo-dati, all'avvio del progetto, vorrei capire fino a che punto mi conviene gestirlo manualmente, ma ciò dipende da cosa succede dopo ila sua creazione: Viene eseguita la Open del DB? Viene solo preparato l'ambiente operativo per agire sul DB, come riconoscimenta del DataSorce, attivazione della connessione, individuazione del percorso d'accesso al DB?
Fino ad ora, a parte avere disegnato gli oggetti Zeos nella scheda del modulo-Dati, ho soltanto personalizzato il relativo file.pas:
unit UModDBcontfam;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, ZCompatibility,
// ZConnection, ZDataset, DB;
ZConnection, ZDataset, SQLite3Conn, SQLDB, DB;
type
{ TDataModule1 }
TDataModule1 = class(TDataModule)
DataSourceX1: TDataSource;
ZConnectionX1: TZConnection;
ZQueryX1: TZQuery;
ZReadOnlyQueryX1: TZReadOnlyQuery;
procedure DataModuleCreate(Sender: TObject);
private
public
end;
var
DataModule1: TDataModule1;
implementation
{$R *.lfm}
{ TDataModule1 }
uses
FrmMain;
procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
ZConnectionX1:= TZConnection.Create(nil);
ZConnectionX1.Database := FrmMain.dbCorr; // dbCorr contiene il percorso del file di DB corrente (Es.: dbProve:= '/media/dirdati/dativari/contabfam/ContabFamdb_prove'
end;
end.
Quindi ho lasciato la creazione automatica del Modulo-Dati.
Ho provato a modificare il mio sorgente considerando ora l'accesso al DB, ma, con l'inserimento del modulo-dati, cambia la mentalità di lettura e scrittura dati, perchè, pur modificando le vecchie istruzioni, ho ricevuo un messaggio d'Errore incomprensibile, per me.
Ecco il codice modificato:
sql:= 'SELECT IdRiepMovg FROM riepmovg';
DataModule1.ZReadOnlyQueryX1.SQL.Text := sql;
DataModule1.ZReadOnlyQueryX1.Active:= True;
totRecQry:= DataModule1.ZReadOnlyQueryX1.RecordCount;
L'Errore è:
Il progetto DomusRatio ha sollevato una eccezione di classe 'EZDatabaseError' con messaggio:
Database connection component is not assigned.
Nel file 'ZAbstractRODataset.pas' alla riga 2536
La riga uses del modulo dati contiene:
unit UModDBcontfam;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, ZCompatibility,
ZConnection, ZDataset, SQLite3Conn, SQLDB, DB;
E' probabile che la proprietà "Connection" del componente ZReadOnlyQueryX1 non è assegnata a una connessione (ZConnection).
E' vero, mi ero dimenticato ad impostarla.
Però, purtroppo le anomalie non sono finite, perchè quando esegue
sql:= 'SELECT IdRiepMovg FROM riepmovg';
DataModule1.ZReadOnlyQueryX1.SQL.Text := sql;
DataModule1.ZReadOnlyQueryX1.Active:= True; <--- questa istruzione
totRecQry:= DataModule1.ZReadOnlyQueryX1.RecordCount;
viene generato un errore logico sulla sql
SQL Error: SQL logic error.
Press OK to ignore and risk data corruption.
Press Abort to kill the program.
Ma la sql precedentemente non ha mai dato problemi.
Il nome del campo "IdRiepMovg" o il nome della tabella "riepmovg" non è corretta (database errato ?). Questo è il motivo dell'errore.
Sembra che sia così come dici tu, ma non è così.
Ho anche detto prima che quella Select, così com'è, funzionava benissimo, quindi perchè dovrebbe essere Errato ora quello che, prima di mettere in pratica il modulo-dati, funzionava benissimo.
Comunque ad evitare ulteriori incomprensioni ho voluto provare a modificare la SELECT, scrivendo
sql:= 'SELECT * FROM riepmovg';
e non ha funzionato, poi ho cambiato tabella e continua a NON funzionare.
In ogni caso, allego un'immagine in cui puoi constatare che i nomi di colonna e tabella sono corretti.
Ho guardato anche il percorso del db ('/media/dirdati/dativari/contabfam/ContabFamdb_prove') ed è corretto. Ho ricontrollato le proprietà degli oggetti ZEOS attuali e precedenti e, secondo me, anche quelle sembrano corrette.
Cosa potrei verificare ancora?
Inghippo scoperto, almeno credo.
Nella procedura DataModuleCreate ho sospeso l'istruzione "ZConnectionX1:= TZConnection.Create(nil);".
Ho riprovato e questa volta l'Errore logico sulla Select non è più comparso. Il programma è andato avanti bene.
implementation
{$R *.lfm}
{ TDataModule1 }
uses
FrmMain;
procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
// ZConnectionX1:= TZConnection.Create(nil); <--- refiuso della prima prova, fatta prima di aggiungere gli oggetti ZEOS
ZConnectionX1.Database := FrmMain.dbCorr;
ZConnectionX1.Connected:= True;
end;
Bene, il ModuloDati comincia ad assumere una sua dimensione propria all'interno di uno dei miei programmi che accedono allo stesso DB.
Ritengo però che debba ancora migliorare la personalizzazione, perchè, come dicevo prima, i programmi in cui devo richiamare il ModuloDati sono già due, ma sicuramente aumenteranno di numero.
Attualmente per accedere al ModuloDati ho inseirto una use nella sezione Implementation, perchè il file ModuloDati.bas (cioè UModuloDatiDB.pas) ed il corrispendente ModuloDati.lfm (cioè UModuloDatiDB.lfm) sono nella stessa directory del progetto
$ ls /media/dirdati/dativari/lazarus_progetti/lazarus_progetti_miei/DomusRatio
Appunti_programma frm3.lfm frmmain.lfm
backup frm3.pas frmmain.pas
bin frm4.lfm griglia_e_matita.jpg
DomusRatioForm2.png frm4.pas grigliaMovvCto_2.png
DomusRatio.ico frm5.lfm grigliaMovvCto.png
DomusRatio.lpi frm5.pas grigliaMovv.png
DomusRatio.lpr frm6.lfm imgdef_domusRatio.png
DomusRatio.lps frm6.pas lib
DomusRatio.res frm7.lfm UModuloDatiDB.lfm
frm2_20211026.pas frm7.pas UModuloDatiDB.pas
frm2.lfm frm8.lfm
frm2.pas frm8.pas
Il ModuloDati, può contenere anche procedure o funzioni per la lettura di certi dati di uso ordinario da parte di più programmi (NON contemporaneamente attivi)?
La directory del ModuloDati può risiedere in un percorso superiore a quello del programma corrente (Es.: /media/dirdati/dativari/lazarus_progetti/lazarus_progetti_miei/)?
Io ho pensato di sì ed ho cercato di realizzare un collegamento dei programmi al ModuloDati comune, tramite una dichiarazione use nella sezione Interface di ciascun programma e l'aggiunta del nuovo percorso nella finestra dell'IDE "Progetto -> Opzioni Progetto -> Percorsi -> Altri file unit", ma ho incontrato diversi ostacoli dovuti a manifestazioni di Errori. Ho dovuto perciò fare marcia indietro e dedicarmi all'utilizzo del ModuloDati, intanto, nel progetto in cui sono impegnato, ma, dopo essere riuscito a compilarlo, senza errori, vorrei apportare le modifiche necessarie per poterlo impiegare anche in altri progetti, senza doverlo riscrivere all'interno di ciascun nuovo progetto.
Spero di avere chiarito il mio concetto.
@ DragoRosso
Grazie per l'incoraggiamento.
Ho rimodificato il mio codice creando, all'interno della directory "/media/dirdati/dativari/lazarus_progetti/lazarus_progetti_miei" la cartella "modul_dati_db" coi file pertinenti al modulo-dati. Ho aggiunto la cartella fra i percorsi di ricerca dell'IDE --> Opzioni progetto -> Percorsi -> altri fil unit. Ho allineato tute le uses delle sezioni Interface dei vari file .pas del progetto ed ho compilato.
Dopo avere eseguito la compilazione senza errori, ho trasferito nel file "UModuloDatiDB.pas" del modulo-dati due funzioni che uso spesso, ottenendo il definitivo "UModuloDatiDB.pas":
unit UModuloDatiDB;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, ZCompatibility,
ZConnection, ZDataset, SQLite3Conn, SQLDB, DB;
type
{ TDataModule1 }
TDataModule1 = class(TDataModule)
DataSourceX1: TDataSource;
ZConnectionX1: TZConnection;
ZQueryX1: TZQuery;
ZReadOnlyQueryX2: TZReadOnlyQuery;
ZReadOnlyQueryX1: TZReadOnlyQuery;
procedure DataModuleCreate(Sender: TObject);
function EstraiCoVoColleg(sql, segnal: String): String;
function EstraiVocePianCont(sql: String): String;
private
public
end;
var
DataModule1: TDataModule1;
implementation
{$R *.lfm}
{ TDataModule1 }
uses
FrmMain;
procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
ZConnectionX1.Database := FrmMain.dbCorr;
ZConnectionX1.Connected:= True;
end;
function EstraiCoVoColleg(sql, segnal: String): String;
var
estratto: String;
begin
DataModule1.ZReadOnlyQueryX2.Active:= False;
DataModule1.ZReadOnlyQueryX2.SQL.Text:= sql;
DataModule1.ZReadOnlyQueryX2.Active:= True;
if (DataModule1.ZReadOnlyQueryX2.RecordCount) > 0 then
begin
DataModule1.ZReadOnlyQueryX2.First;
case segnal of
'S', 'P':
begin
estratto:= DataModule1.ZReadOnlyQueryX2.FieldByName('NumVoColleg').AsString;
end
else begin
estratto:= '*?*'; // CODICE DI errore PER record NON TROVATO
end;
end;
end;
Result:= estratto;
end;
function EstraiVocePianCont(sql: String): String; // Estrae la Voce contabile, a cui accoda il contenuto del campo "ContrPartSiNo"
var
swOpenErro: Boolean = False;
estratto: String;
begin
WriteLn('function EstraiVocePianCont');
DataModule1.ZReadOnlyQueryX2.Active:= False;
DataModule1.ZReadOnlyQueryX2.SQL.Text:= sql;
DataModule1.ZReadOnlyQueryX2.Active:= True;
if (DataModule1.ZReadOnlyQueryX2.RecordCount) > 0 then
begin
DataModule1.ZReadOnlyQueryX2.First;
estratto:= DataModule1.ZReadOnlyQueryX2.FieldByName('NomeVoce').AsString;
estratto:= estratto + DataModule1.ZReadOnlyQueryX2.FieldByName('ContrPartSiNo').AsString;
end
else begin
estratto:= '*?*'; // CODICE DI errore PER recor NON TROVATO
end;
Result:= estratto;
end;
end.
Ebbene, la chiamata di una qualsiasi delle funzioni appena spostate, produce il seguente messaggio d'Errore:
frm6.pas(560,23) Error: Identifier not found "EstraiVocePianCont"
frm6.pas(570,36) Error: Identifier not found "EstraiCoVoColleg"
Non capisco perchè vengono ignorate. Eppure avevo percepito che avrei potuto aggiungere nel modulo-dati anche funzioni e procedure.
NELLA IMPLEMENTATION: DEVI IMPLEMENTARE LE FUNZIONI CON L'INTESTAZIONE "TDATAMODULE1." !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Visto che appartengono a quella classe.
Ho fatto come mi hai indicato, ma il messaggio d'Errore ricompare tale e quale:
unit UModuloDatiDB;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, ZCompatibility,
ZConnection, ZDataset, SQLite3Conn, SQLDB, DB;
type
{ TDataModule1 }
TDataModule1 = class(TDataModule)
DataSourceX1: TDataSource;
ZConnectionX1: TZConnection;
ZQueryX1: TZQuery;
ZReadOnlyQueryX2: TZReadOnlyQuery;
ZReadOnlyQueryX1: TZReadOnlyQuery;
procedure DataModuleCreate(Sender: TObject);
function EstraiCoVoColleg(sql, segnal: String): String;
function EstraiVocePianCont(sql: String): String;
private
public
end;
var
DataModule1: TDataModule1;
implementation
{$R *.lfm}
{ TDataModule1 }
procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
ZConnectionX1.Connected:= True;
end;
function TDataModule1.EstraiCoVoColleg(sql, segnal: String): String; // Estrae dalla Tabella "racodvoci" il codice di sottoconto contrappoto al tipo inmdicato in "segnal":
// se "segnal = "S" o "P", la viariabile "coVoSotCto" contiene un codice di Sottoconto di Cassa
// se "segnal = "N", la viariabile "coVoSotCto" contiene un codice di Sottoconto di Contropartita
var
estratto: String;
begin
DataModule1.ZReadOnlyQueryX2.Active:= False;
DataModule1.ZReadOnlyQueryX2.SQL.Text:= sql;
DataModule1.ZReadOnlyQueryX2.Active:= True;
if (DataModule1.ZReadOnlyQueryX2.RecordCount) > 0 then
begin
DataModule1.ZReadOnlyQueryX2.First;
. . .
end;
function TDataModule1.EstraiVocePianCont(sql: String): String; // Estrae la Voce contabile, a cui accoda il contenuto del campo "ContrPartSiNo"
var
swOpenErro: Boolean = False;
estratto: String;
begin
. . .
frm6.pas(614,19) Error: Identifier not found "EstraiVocePianCont"
frm6.pas(627,23) Error: Identifier not found "EstraiVocePianCont"
Non puoi usare in quel modo le due funzioni. Appartengono ad una classe e devi usarle con l'istanza della classe:
strimia := classe.EstraiVocePianCont(sql);
Non puoi usare in quel modo le due funzioni. Appartengono ad una classe e devi usarle con l'istanza della classe:
strimia := classe.EstraiVocePianCont(sql);
L'avevo provata pure, ma mi da sempre Errore:
striMia:= TDataModule1.EstraiVocePianCont(sql); <--- così da l'Errore riportato sotto
frm6.pas(614,55) Error: Only class methods, class properties and class variables can be accessed in class methods
frm6.pas(614,55) Error: Only class methods, class properties and class variables can be referred with class references
Però
striMia:= DataModule1.EstraiVocePianCont(sql); <--- così funziona
Purtroppo non è finita.
Ho ancora difficoltà perchè non riesco ad impostare il percorso del mio database.
Nel modulo-dati, avevo impostato il percorso così:
uses
FrmMain;
procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
ZConnectionX1.Database := FrmMain.dbCorr;
ZConnectionX1.Connected:= True;
end;
E funzionava, però mi sono reso conto che il modulo dati rimaneva agganciato al progetto utente per via della "uses FrmMain", dove sono in grado di capire quale database fra (dbOrig e dbProve) è quello attivo.
Ho voluto perciò togliere quel vincolo. Per farlo, ho pensato di definire nel modulo-dati una variabile (UdbCorr) da valorizzare nella procedure TForm1.FormCreate del progetto utente.
dichiarazione variabile nel modulo dati:
unit UModuloDatiDB;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils, ZCompatibility,
ZConnection, ZDataset, SQLite3Conn, SQLDB, DB;
type
{ TDataModule1 }
TDataModule1 = class(TDataModule)
DataSourceX1: TDataSource;
ZConnectionX1: TZConnection;
ZQueryX1: TZQuery;
ZReadOnlyQueryX2: TZReadOnlyQuery;
ZReadOnlyQueryX1: TZReadOnlyQuery;
procedure DataModuleCreate(Sender: TObject);
function EstraiCoVoColleg(sql, segnal: String): String;
function EstraiVocePianCont(sql: String): String;
private
public
var
UdbCorr: String;
end;
var
DataModule1: TDataModule1;
implementation
{$R *.lfm}
{ TDataModule1 }
//uses
// FrmMain;
procedure TDataModule1.DataModuleCreate(Sender: TObject);
begin
// ZConnectionX1.Connected:= False;
// ZConnectionX1.Database := FrmMain.dbCorr;
ZConnectionX1.Database := UdbCorr;
ZConnectionX1.Connected:= True;
end;
Poi nel progetto utente ho scritto:
procedure TForm1.FormCreate(Sender: TObject);
var
begin
. . .
//------------------------------------ Assegno percorso per l'accesso al DB corretto
dbCopia:= '/media/dirdati/dativari/contabfam/ContabFamdb(copia)';
dbOrig:= '/media/dirdati/dativari/contabfam/ContabFamdb';
dbProve:= '/media/dirdati/dativari/contabfam/ContabFamdb_prove';
//------------------------------------------------------------------------------------
DataModule1.UdbCorr:= dbProve;
Ebbene, ques'ultima istruzione provoca il seguente Errore:
Il progetto DomusRatio ha sollevato una eccezione di classe 'External: SIGSEGV'.
All'indirizzo 42A20E
Ho provato a spostare la dichiarazione della variabile nel modulo-dati ma inutilmente. Anche questa volta non ho capito come risolvere.