I'm trying to code a function that creates a TADOQuery dynamically. With this function, I'm able to change its SQL.Text property in the parameter of the function. How my function call should work:
procedure TDlgMain.FormCreate(Sender: TObject);
var
Q: TADOQuery;
begin
Q := NewQuery('select * from Utenti');
Q.Open;
end;
Here the code of the function and a screenshot of the access violation error, is there a way to solve this?
function NewQuery(Conn: TADOConnection; SQL: String): TADOQuery; overload;
function NewQuery(SQL: String): TADOQuery; overload;
function TDMDB.NewQuery(Conn: TADOConnection; SQL: String): TADOQuery;
begin
Result := TADOQuery.Create(nil);
Result.Connection := Conn;
Result.SQL.Text := SQL;
end;
function TDMDB.NewQuery(SQL: String): TADOQuery;
begin
Result := NewQuery(DBConn, SQL);
end;
The error indicates that the TDataModule is not yet created at the time you attempt to use it to create the query via DMDB.NewQuery().
The reason for the error is two folded.
First, when using the IDE to first create a form (DlgMain: TDlgMain in your case) and then a data module (TDMDB: TDataModule). This places the module creation after the form creation in the project file (.dpr) as folllows: (to see the .dpr file, select menu Project - View source)
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TDlgMain, DlgMain);
Application.CreateForm(TDMDB, DMDB);
Application.Run;
end.
Secondly, as you attempt to create the connection already in the main forms OnCreate() event, when the data module is not yet created, the result is the AV you see.
You can correct the error by moving the creation of your data module before the creation of your main form:
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TDMDB, DMDB); // Create this before the form
Application.CreateForm(TDlgMain, DlgMain);
Application.Run;
end.
A side note, in case you wonder:
You may be aware that the first form created via Application.CreateForm() becomes the main form, and that is still valid. The data module is not a form, and thus your DlgMain is still the first created form and becomes the main form.
Related
I would like to save a file as a "read-only" file. Is it possible?
This is a two stage process, that is only available on the Windows platform.
Save the file.
Add the read-only attribute to the file's metadata.
Assuming that you already know how to do stage 1, let's consider stage 2. Using the System.IOUtils unit you set this attribute like so:
uses
System.IOUtils;
....
var
attributes: TFileAttributes;
....
attributes := TFile.GetAttributes(FileName);
Include(attributes, faReadOnly);
TFile.SetAttributes(FileName, attributes);
If you wish to remove the read-only attribute then you use exactly the same code but replace Include with Exclude.
For platforms other than Windows, you can still use TFile.GetAttributes and TFile.SetAttributes, but the available attributes are very different, reflecting the different filesystem models of Windows and the POSIX platforms.
Unfortunately the RTL fails to provide any way to check for errors in this code. So if you want to check for errors (you should) then you are actually best calling the Windows API function SetFileAttributes directly. You might do that like this:
function FileSetAttribute(const FileName: string; const Attr: DWORD; const Value: Boolean): Boolean;
var
Flags, NewFlags: DWORD;
begin
Flags := GetFileAttributes(PChar(FileName));
if Flags=INVALID_FILE_ATTRIBUTES then begin
Result := False;
end else begin
if Value then begin
NewFlags := Flags or Attr
end else begin
NewFlags := Flags and not Attr;
end;
Result := (NewFlags=Flags) or SetFileAttributes(PChar(FileName), NewFlags);
end;
end;
function FileSetReadOnly(const FileName: string; ReadOnly: Boolean): Boolean;
begin
Result := FileSetAttribute(FileName, faReadOnly, ReadOnly);
end;
As discussed previously this code is for Windows only. The FileSetReadOnly function returns a boolean indicating whether or not it succeeded. In case of failure, you can then call GetLastError for extended error information.
Save it as normal, f.ex. via
VAR S : TFileStream;
.
S := TFileStream.Create(FileName,fmCreate);
<Write to stream>
S.Free;
Then
USES SysUtils;
// Set R/O
IF FileSetAttr(FileName,FileGetAttr(FileName) or faReadOnly)<>NO_ERROR THEN
RaiseLastOSError;
afterwards to mark it as "Read/Only".
If you want to update it later on, you need to remove the Read/Only flag first:
// Set R/W
IF FileSetAttr(FileName,FileGetAttr(FileName) AND NOT faReadOnly)<>NO_ERROR THEN
RaiseLastOSError;
then update the file, then mark it as Read/Only again.
I recently started using the [ExecSQLScalar]1 and [ExecSQL]2 methods of the FDConnection component in Delphi XE5. It's very handy not to need to build a Dataset object, like FDQuery just for simple queries or executions.
However I had a curious problem when executing a function with void return that has internal validations where it can generate exceptions. I'm using a Postgres database.
CREATE FUNCTION can_be_exception()
RETURNS void AS
$$
BEGIN
RAISE EXCEPTION E'fail';
END;
$$
LANGUAGE plpgsql STABLE;
In delphi, I call the ExecSQLScalar function ...
FDConnection1.ExecSQLScalar('select 1');
FDConnection1.ExecSQLScalar('select can_be_exception()');
On first run, I get the following error:
Project TFDConnectionDEMO.exe raised exception class
EPgNativeException with message '[FireDAC][Phys][PG][libpq] ERROR:
fail'.
On the second run, I get a Violation Access error:
Project TFDConnectionDEMO.exe raised exception class $C0000005 with
message 'access violation at 0x00000000: read of address 0x00000000'.
Apparently the error occurs in the line below in unit FireDAC.Comp.Client
function TFDCustomConnection.ExecSQLScalar(const ASQL: String;
const AParams: array of Variant; const ATypes: array of TFieldType): Variant;
var
oCmd: IFDPhysCommand;
begin
oCmd := BaseCreateSQL;
try
if BasePrepareSQL(oCmd, ASQL, AParams, ATypes) or (FExecSQLTab = nil) then begin
FDFree(FExecSQLTab);
...
ignoring the previous error and trying again, another error is displayed...
Project TZConnectionDEMO.exe raised exception class EFDException with
message '[FireDAC][DatS]-24. Row is not nested'.
Searching, I found no response to this error. I figured my mistake would be to call the bank raise_exception function using the ExecSQLScalar function of the FDConnection component. So I tried using FDConnection.ExecSQL and as I imagined, you can not use this if there is a SELECT clause in the parameter.
Is there a better way to call function with void return using FDConnection.ExecSQL? would a BUG be in the component? or would not it be correct to make that kind of call?
Using ExecSQLScalar is fine in this case. This is certainly a bug (which was already fixed, at least in Delphi 10.2.3). As you've correctly pointed out, the problem is in releasing a table storage object instance held by the FExecSQLTab field by using FDFree procedure.
I don't have Delphi XE5 source code but maybe you can see something like this inside (comments about what happened are added by me):
if BasePrepareSQL(oCmd, ASQL, AParams, ATypes) or (FExecSQLTab = nil) then
begin
FDFree(FExecSQLTab); { ← directly calls destructor if object is not nil }
FExecSQLTab := oCmd.Define; { ← no assignment if command execution raises exception }
end;
Problem was that when a SQL command execution raised exception during storage table definition stage (oCmd.Define), reference to previously destroyed storage table object instance (by FDFree) remained stored in the FExecSQLTab field (as a dangling pointer).
Then when a different command was executed that way, FDFree procedure was called just for that dangling pointer. Hence the access violation.
Way to correct this is replacing line e.g. by:
FDFree(FExecSQLTab);
by:
FDFreeAndNil(FExecSQLTab);
which was done in some later Delphi release.
I'm using Delphi 10.1 Berlin and I needed to use TAdoStoredProc in my project. I don't use any non-visual component in my forms or data modules. All my connection and db components are creating at run-time.
My Question: When I'm trying to execute TAdoStoredProc, it executes twice and my insert, update processes have worked twice.
var
mSp: TADOStoredProc;
i: Integer;
begin
mSp := TADOStoredProc.Create(nil);
mSp.Connection := conn;
mSp.ProcedureName := spname;
mSp.CommandTimeout := 600;
mSp.parameters.Refresh;
for i := 0 to parameters.Count - 1 do
begin
mSp.parameters.ParamByName(parameters[i].Name).Value := parameters[i].Value;
end;
mSp.ExecProc;
mSp.Open;
Result := mSp;
What should I do or change? Thanks.
As I see that your code required some return values. So you should use TAdoStoredProc like TAdoQuery. You don't need to use ExecProc function for execute, try to use only Open function.
You can just check out TADOStoredProc.ExecProc Method documentation from here.
If an application is only interested in the result set returned by a stored procedure, call Open method for the TADOStoredProc component or set its Active property to true.
i use dynamic array in delphi.
var
var
frame3:array[0..10] of TFrame3
procedure TForm1.Button1Click(sender:TObject);
begin
frame3[count] := TFrame3.create(self);
gridpanel2.insertcontrol(frame3[count]);
but this code is 'A component named Frame3 alredy exists.'
this error what can ido?
If you need multible instances of TFrame3 you need to give it a new name after you created it.
so change
frame3[count] := TFrame3.create(self);
gridpanel2.insertcontrol(frame3[count]);
To
frame3[count] := TFrame3.create(self);
frame3[count].Name := 'Frame3_' + InttoStr(Count);
gridpanel2.insertcontrol(frame3[count]);
The other issue is that it is not seen in your code how you are changing count loop variable. Is it defined in advance?
You need to do something like this:
procedure TForm1.Button1Click(sender:TObject);
var count:byte;
begin
for count:=1 to 10 do
begin
frame3[count] := TFrame3.create(self);
...
end;
or use any other way to set count before each array member (class instance) creation. This code will even probably not require to set Name property at all.
I need to create a class containing an array of record objects but trying to use SetLength raise an Access Violation errror.
Consider the following example of a tree object with fruits.
type
TFruit = record
color: string;
weight: double;
end;
type
TObjectTree = class
Public
Fruits: array of TFruit;
constructor Create;
procedure AddFruit;
end;
In the implementation, when trying to resize the array of Fruit objects or initializing to nil generates problems.
constructor TObjectTree.Create;
begin
inherited Create;
Fruits:=nil; //Raises an error
end;
procedure TObjectTree.AddFruit(FruitColor: string; FruitWeight: integer);
begin
SetLength(Fruits, Length(Fruits)+1); //Raises an error (when I comment Fruits:=nil; in the constructor)
Fruits[Length(Fruits)].color:=FruitColor;
Fruits[Length(Fruits)].weight:=FruitWeight;
end;
How can I use dynamic arrays in a class?
Replace
Fruits[Length(Fruits)].color:=FruitColor;
Fruits[Length(Fruits)].weight:=FruitWeight;
with
Fruits[High(Fruits)].color:=FruitColor;
Fruits[High(Fruits)].weight:=FruitWeight;
then it works.
Something tells me you've neglected to create an instance of TObjectTree. You've declared a TObjectTree variable, but you've either not called TObjectTree.Create, or you've called it directly on the variable you declared instead of assigning a new value to that variable:
var
Tree: TObjectTree;
begin
// This is wrong.
Tree.Create;
// This is right.
Tree := TObjectTree.Create;
Without properly instantiating TObjectTree, there is no valid memory to back the Fruits field you attempt to use, so assigning a value to it gives an error.
As an addition to the answers of iamjoosy and Rob Kennedy, I would code this like so:
procedure TObjectTree.AddFruit(FruitColor: string; FruitWeight: integer);
var
NewCount: Integer;
begin
NewCount := Length(Fruits)+1;
SetLength(Fruits, NewCount);
Fruits[NewCount-1].color := FruitColor;
Fruits[NewCount-1].weight := FruitWeight;
end;
It is clearer, in my view, to call Length() just once.
You do not need to assign Fruits := nil in the constructor since that happens automatically. All fields are zero-initialised when an object is instantiated. That said, Fruits := nil should not raise an error. If it does it is probably a result of a memory corruption due to the out-of-bounds array accessing.
A further point to make is that enabling range checking would have resulted in an informative error that would have explained the problem. This is much more helpful than relying on access violations. I can't recommend range checking highly enough.
Finally, the SetLength(..., Length(...)+1) pattern typically leads to very inefficient memory usage and can lead to performance problems for large lists. If you have Delphi 2009+, I would recommend using TList<TFruit> instead.