Should I outsource my database connection in delphi? - sql

My program keeps making new requests to a database while it's running. Now, I am wondering if I should outsource my database connection, since it always creates a new one for a new query and destroys it afterwards.
function TMainMenu.btnInsertPersonClick(Sender: TObject)
var
vDatabase : TADOConnection;
vQuery : TADOQuery;
begin
vDatabase := TADOConnection.Create(self);
vQuery := TADOQuery.Create(self);
try
vDatabase.ConnectionString := 'SECRET_STRING';
vQuery.Connection := vDatabase;
// DO QUERY STUFF HERE
finally
vDatabase.Close;
vQuery.Destroy;
vDatabase.Destroy;
end;
end;
Maybe it's not a good idea to outsource the connection. That's why I am happy about any help or suggestions. Sheers!

Related

Delphi 10.2: Using Local SQL with Firedac Memory Tables

How can I use Firedac LocalSQL with FDMemtable? Is there any working example available?
According to the Embarcadero DocWiki I set up a local connection (using SQLite driver), a LocalSQL component and connected some Firedac memory tables to it. Then I connected a FDQuery and try to query the memory tables. But the query always returns "table xyz not known" even if I set an explicit dataset name for the memory table in the localSQL dataset collection.
I suspect that I miss something fundamental that is not contained in the Embarcadero docs. If anyone has ever got this up and running I would be grateful for some tips.
Here is some code I wrote for an answer here a while ago, which is a self-contained example of using LocalSQL, tested in D10.2 (Seattle). It should suffice to get you going. Istr that the key to getting it working was a comment somewhere in the EMBA docs that FD's LocalSQL is based on Sqlite, as you've noted.
procedure TForm3.CopyData2;
begin
DataSource2.DataSet := FDQuery1;
FDConnection1.DriverName := 'SQLite';
FDConnection1.Connected := True;
FDLocalSQL1.Connection := FDConnection1;
FDLocalSQL1.DataSets.Add(FDMemTable1);
FDLocalSQL1.Active := True;
FDQuery1.SQL.Text := 'select * from FDMemTable1 order by ID limit 5';
FDQuery1.Active := True;
FDMemTable1.Close;
FDMemTable1.Data := FDQuery1.Data;
end;
procedure TForm3.FormCreate(Sender: TObject);
var
i : integer;
MS : TMemoryStream;
begin
FDMemTable1.CreateDataSet;
for i := 1 to 10 do
FDMemTable1.InsertRecord([i, 'Row:' + IntToStr(i), 10000 - i]);
FDMemTable1.First;
// Following is to try to reproduce problem loading from stream
// noted by the OP, but works fine
MS := TMemoryStream.Create;
try
FDMemTable1.SaveToStream(MS, sfBinary);
MS.Position := 0;
FDMemTable1.LoadFromStream(MS, sfBinary);
finally
MS.Free;
end;
end;
As you can see, you can refer in the SQL to an existing FireDAC dataset simply by using its component name.

Stored procedures in delphi

I'm trying to call a stored procedure from some delphi code
I have a function like
procedure TDatabaseConnection.GetHourlyFiltergramLabSamples(StartTime, EndTime : TDateTime; Samples : TList<THourlyFilterCount>);
var
StoredProc : TADOStoredProc;
begin
StoredProc := TADOStoredProc.Create(nil);
try
StoredProc.Connection := Connection;
StoredProc.ProcedureName := 'GetHourlyFiltergramLabSamples';
StoredProc.Parameters.Refresh;
StoredProc.Parameters.ParamByName('#StartTime').Value := startTime;
StoredProc.Parameters.ParamByName('#EndTime').Value := EndTime;
StoredProc.Open;
while not StoredProc.Eof do
begin
//Do stuff with the results here ...
StoredProc.Next;
end;
finally
FreeAndNil(StoredProc);
end;
end;
When I hit the line StoredProc.Open;
I get an error
'CommandText does not return a result set.'
I have checked using SQL server management studio that the stored procedure does in fact return results.
I've found that this issues seems to be temperamental. Surely this isn't a bug in the database connector??
I'm out of ideas
I've had this one open for a number of months now.
The best solution I have is to switch to using FireDAC. I have not had the same issues when using FireDAC to execute stored procedures.
instead I use the TFDStoredProc type to run stored procedures

Lazarus sqldb and Transactions

I'm developing an application (for Win32 and WinCE) in Lazarus using sqldb components for data access.
Remote database is PostgreSQL (but i have the same behaviour with local SQLite).
Connection to PostgreSQL work perfectrly but when I open any Query (also a very simple select) database go in transaction: "idle in transaction".
var
PGConnection:TPQConnection;
PGTransaction:TSQLTransaction;
myQuery:TSQLQuery;
begin
PGConnection := TPQConnection.Create(self);
PGTransaction := TSQLTransaction.Create(self);
myQuery := TSQLQuery.Create(self);
try
PGConnection.HostName := '192.168.1.2';
PGConnection.DatabaseName:='testdb';
PGConnection.UserName:='test';
PGConnection.Password:='test';
PGConnection.Transaction := PGTransaction;
PGConnection.Open;
myQuery.DataBase := PGConnection;
myQuery.SQL.Add('SELECT 1 AS value');
myQuery.Open; // <- this start transaction
ShowMessage(myQuery.FieldByName('value').AsString); // <- db: "idle in transaction"
myQuery.Close; // <- db: "idle in transaction"
PGConnction.Close;
finally
myQuery.Free;
PGConnection.Free;
PGTransaction.Free;
end;
end;
Ok, maybe sqldb work in this way: every query on database start a transaction, so, developer must Commit or Rollback after interrogation. But there is another question: when I commit the Transaction, sqldb close the query and i can't access value retrieved:
var
PGConnection:TPQConnection;
PGTransaction:TSQLTransaction;
myQuery:TSQLQuery;
begin
PGConnection := TPQConnection.Create(self);
PGTransaction := TSQLTransaction.Create(self);
myQuery := TSQLQuery.Create(self);
try
PGConnection.HostName := '192.168.1.2';
PGConnection.DatabaseName:='testdb';
PGConnection.UserName:='test';
PGConnection.Password:='test';
PGConnection.Transaction := PGTransaction;
PGConnection.Open;
myQuery.DataBase := PGConnection;
myQuery.SQL.Add('SELECT 1 AS value');
myQuery.Open; // <- this start transaction
PGConnection.Transaction.Active := False; // <- Close also myQuery
ShowMessage(myQuery.FieldByName('value').AsString); // <- Error: Field "value" not found
myQuery.Close;
PGConnction.Close;
finally
myQuery.Free;
PGConnection.Free;
PGTransaction.Free;
end;
end;
This behavior is a bit boring: I can't use TSQLQuery dataset with dbgrid (since I do not want my database in transaction for too long) so I need to move selected data in Memory Tables.
Is this a bug, I made some mistakes or is a normal operation? There is a way to open a SELECT Query and use it without start a transaction?
This is currently normal behaviour.
I have planned an 'offline' mode where the transaction is closed but the data is kept open.
What you can currently do is save the data to file (using the savetofile method), disconnect, and load the data again from file (using the loadfromfile method)

delphi has ExecSQL succeeded or failed

Hello i've this function to update my Access DB using TUniQuery :
var
Res:Boolean;
begin
Res:=false;
try
with MyQuery do
begin
Active := false;
SQL.Clear;
SQL.Add('Update MYTABLE');
SQL.Add('set username='+QuotedStr(NewUserName));
SQL.Add(',password='+QuotedStr(NewPassword));
SQL.Add('where username='+QuotedStr(ACurrentUserName));
ExecSQL;
Res:=true;
end;
except
Res:=False;
end ;
Result:=Res;
end;
Is the use of Try ... except enough to KNOW when " ExecSQL " succeeds or fails ?
or is there any other better approach ?
thank you
You may want to consider your update succeeded if no exception is raised. It means the database is responsive and parsed and executed your statement without syntax errors.
In a statement like shown, you may also want to be sure that exactly one row was updated, because I assume that's your intention.
To check that, you can resort to the result of the ExecSQL method, which returns the number of rows affected by the execution of the statement. So, you may change your code to this:
begin
with MyQuery do
begin
Active := false;
SQL.Clear;
SQL.Add('Update MYTABLE');
SQL.Add('set username='+QuotedStr(NewUserName));
SQL.Add(',password='+QuotedStr(NewPassword));
SQL.Add('where username='+QuotedStr(ACurrentUserName));
Result := ExecSQL = 1; //exactly 1 row updated
end;
end;
I also changed the unconditional exception handler, since it may not be the proper site to eat any exception, and also removed the local variable to store the Result, since that really is not necessary.
After reading your added text and re-thinking your question:
Is the use of Try ... except enough to KNOW when " ExecSQL " succeeds or fails ?
You really have to change your mind about exception handling and returning a boolean from this routine. Exceptions were introduced as a whole new concept on how to address exceptional and error situations on your programs, but you're killing this whole new (and IMHO better) approach and resorting to the old way to return a value indicating success or failure.
Particularly a try/exception block that eats any exception is a bad practice, since you will be killing exceptions that may be raised for too many reasons: out of memory, network problems like connection lost to database, etc.
You have to re-think your approach and handle these exceptional or error conditions at the appropriate level on your application.
My advise is:
change this from a function to a procedure, the new contract is: it returns only if succeded, otherwise an exception is raised.
if an exception occurs let it fly out of the routine and handle that situation elsewhere
raise your own exception in case no exactly 1 row is updated
change the query to use parameters (avoiding sql injection)
The routine may look like this:
procedure TMySecurityManager.ChangeUserNameAndPassword();
begin
MyQuery.SQL.Text := 'Update MYTABLE'
+ ' set username = :NewUserName'
+ ' , password = :NewPassword'
+ ' where username = :username';
MyQuery.Params.ParamByName('NewUserName').AsString := NewUserName;
MyQuery.Params.ParamByName('NewPassword').AsString := NewPassword;
MyQuery.Params.ParamByName('username').AsString := ACurrentUserName;
if MyQuery.ExecSQL <> 1 then
raise EUpdateFailed.Create('A internal error occurred while updating the username and password');
//EUpdateFailed is a hypotetical exception class you defined.
end;

Where to perform final processing before application closes?

I have been trying to write out data to storage when the form closes or the application terminates and have NOT been successful.
I first tried from the Form unit
procedure TForm1.FinalizeObject;
begin
inherited;
SaveData;
end;
and
procedure TForm1.FinalizeObject;
begin
SaveData;
inherited;
end;
neither of these attempts worked, therefore I rrearranged my code and tried it from the project’s unit
procedure TApplication.ApplicationClosing;
begin
SaveData;
inherited;
end;
and
procedure TApplication.ApplicationClosing;
begin
inherited;
SaveData;
end;
I have a w3_showmessage as the first line of SaveData, and it never gets called ever....so, if i can verify that one of these 4 methods is triggered, i could use one of them
What am i doing wrong? Thanks
As of writing, Smart exposes two new events in the application object:
OnUnload
OnBeforeUnload
These will make it easier to handle shutdown sequences. The smart javascript bootstrap also calls application.terminate() automatically now, so your code should work fine.