I would like to expose a function that can take an optional anonymous method :
type
TParamsProc = reference to procedure(Params: TSQLParams);
TFieldsProc = reference to procedure(Fields: TSQLResult);
TDbController = class
...
public
procedure Select(const SQL: sting; ParamsProc: TParamsProc; FieldsProc: TFieldsProc);
end;
implementation
procedure TDbController.Select(const SQL: sting; ParamsProc: TParamsProc; FieldsProc: TFieldsProc);
var
Q: TUIBQuery;
begin
Q := TUIBQuery.Create(nil);
try
Q.Database := FDatabase;
Q.Transaction := FTransaction;
Q.SQL.Text := SQL;
ParamsProc(Q.Params);
Q.Open;
while not Q.Eof do
begin
FieldsProc(Q.Result);
Q.Next;
end;
finally
Q.Free;
end;
end;
As sometimes I have no params to pass to a SQL Query, I would like to make the ParamsProc optional.
this code don't work :
if ParamsProc <> nil then ParamsProc(Q.Params);
nor this one :
if #ParamsProc <> nil then ParamsProc(Q.Params);
The first one don't compile, the second one compile but don't work because ParamsProc has always a non nil value.
Example of call :
FController.Select(
'select A, B, C from SOME_TABLE',
nil,
procedure(Fields: TSQLResult)
begin
FA := Fields.AsInteger[0];
FB := Fields.AsString[1];
FC := Fields.AsCurrency[2];
end
);
Edit
Seems that Assigned(ParamsProc) do the trick.
Following Jeroen Pluimers advice, I make my "Edit" an "Answer" :
Assigned(ParamsProc) do the trick :
procedure TDbController.Select(const SQL: sting; ParamsProc: TParamsProc; FieldsProc: TFieldsProc);
var
Q: TUIBQuery;
begin
Q := TUIBQuery.Create(nil);
try
Q.Database := FDatabase;
Q.Transaction := FTransaction;
Q.SQL.Text := SQL;
if Assigned(ParamsProc) then
ParamsProc(Q.Params);
Q.Open;
while not Q.Eof do
begin
FieldsProc(Q.Result);
Q.Next;
end;
finally
Q.Free;
end;
end;
Hope this helps !
Related
Am learning how to use insert into statements and, with my access database, am trying to insert a single record. The table I'm inserting a new record into has three fields: StockID (AutoN), Description (Text), Cost (Number). I've looked at previous posts but the posted solutions seem to go beyond my basic level of Insert Into...which is what I'm interested in. Anyway, here is my code...
adoQuery1.Active := true;
adoQuery1.SQL.Clear;
adoQuery1.SQL.Add('INSERT INTO Stock (StockID,Description,Cost) VALUES (4,Cheese,5)');
adoQuery1.open;
adoQuery1.Close;
It compiles fine, but when press a command button to invoke the above, I get the following message:
'ADOQuery1: "Missing SQL property".'
what am I doing wrong?
Thanks, Abelisto. Your last post looks complex indeed...but I did my own little version since your last solution got me up and running. It works so I'm very chuffed. Am now going to focus on DELETE FROM using combobox (for field selection) and user value. Here was my solution I got working... ;)
x:=strtoint(txtStockID.Text);
y:=txtDescription.Text;
z:=strtoCurr(txtCost.Text);
adoQuery1.SQL.Clear;
adoQuery1.SQL.Add('INSERT INTO tblStock (StockID,Description,Cost)');
adoQuery1.SQL.Add('VALUES (:StockID,:Description,:Cost)'); // ':StockID' denotes a parameter
adoQuery1.Parameters.ParamByName('StockID').Value:= x;
adoQuery1.Parameters.ParamByName('Description').Value:= y;
adoQuery1.Parameters.ParamByName('Cost').Value:= z;
adoQuery1.ExecSQL;
adoQuery1.Close;
Using parameters is more efficient then constant SQL statements.
Additional to my comments here is some useful functions which I using frequently to call SQL statements with parameters (Maybe it will be useful for you too):
function TCore.ExecQuery(const ASQL: String; const AParamNames: array of string;
const AParamValues: array of Variant): Integer;
var
q: TADOQuery;
i: Integer;
begin
if Length(AParamNames) <> Length(AParamValues) then
raise Exception.Create('There are different number of parameter names and values.');
q := GetQuery(ASQL) as TADOQuery;
try
for i := Low(AParamNames) to High(AParamNames) do
SetParamValue(q, AParamNames[i], AParamValues[i]);
q.ExecSQL;
Result := q.RowsAffected;
finally
q.Free;
end;
end;
function TCore.GetQuery(const ASQL: String): TDataSet;
begin
Result := TADOQuery.Create(Self);
(Result as TADOQuery).CommandTimeout := 0;
(Result as TADOQuery).Connection := Connection;
(Result as TADOQuery).SQL.Text := ASQL;
end;
procedure TCore.SetParamValue(AQuery: TDataSet; const AName: string; const AValue: Variant);
var
i: Integer;
q: TADOQuery;
begin
q := AQuery as TADOQuery;
for i := 0 to q.Parameters.Count - 1 do
if AnsiSameText(AName, q.Parameters[i].Name) then
begin
case VarType(AValue) of
varString, varUString:
q.Parameters[i].DataType := ftString;
varInteger:
q.Parameters[i].DataType := ftInteger;
varInt64:
q.Parameters[i].DataType := ftLargeint;
end;
q.Parameters[i].Value := AValue;
end;
end;
And usage example in your case:
Core.ExecQuery(
'INSERT INTO Stock (StockID, Description, Cost) VALUES (:PStockID, :PDescription, :PCost)',
['PStockID', 'PDescription', 'PCost'],
[4, 'Cheese', 5]);
This is a bit confusing but will try best to explain it. please ask if you need more details.
First i have a class called TPlayers Like so..
TPlayers = class
Private
p : array[1..20] of TStringList;
function GetPlayer(i:integer): TStringList;
Public
Property player[i : integer] : TStringList read GetPlayer;
constructor Create; virtual;
implementation
uses
main;
{constructor}
constructor TPlayers.Create;
begin
p[1] := TStringList.Create;
p[2] := TStringList.Create;
p[3] := TStringList.Create;
p[4] := TStringList.Create;
p[5] := TStringList.Create;
p[6] := TStringList.Create;
end;
function TPlayers.GetPlayer(i: integer): TStringList;
begin
Result := p[i];
end;
I now have FTherePlayers := TPlayers.Create to create the class.
First time i add to the stringlist like so
FTherePlayers.Player[strtoint(name2)].Add('posx='+inttostr(posL.x));
or with variables taken out
FTherePlayers.Player[1].Add('posx=15');
This seems to be fine, but next i try to update it like so
FTherePlayers.Player[strtoint(ID)].Values['posx='] := xpos;
or with variables taken out
FTherePlayers.Player[1].Values['posx='] := 12;
but then i check that value after changing it and it still says 15, thus when i do
showmessage(fthereplayers.player[1].Values['posx']);
it returns 15 but it should be 12. Any idea why its not changeing?
thanks
Glen
You have an extra equals sign at the end of the Name index value of the Values property. You need to use only the name portion of a name value pair without the equals sign. So, in your code just replace the following lines:
// here is an extra equals sign in 'posx=' index value
FTherePlayers.Player[1].Values['posx='] := 12;
FTherePlayers.Player[strtoint(ID)].Values['posx='] := xpos;
with this:
FTherePlayers.Player[1].Values['posx'] := 12;
FTherePlayers.Player[strtoint(ID)].Values['posx'] := xpos;
I have a function declare like this :
function execProc(ProcName,InValues:PChar;out OutValues:PChar):integer; //The "OutValues" is a out parameter.
And I call this function like this:
procedure TForm1.Button6Click(Sender: TObject);
var
v:integer;
s:pchar;
begin
Memo1.Clear;
v := execProc(pchar('PROC_TEST'),pchar('aaa'),s);
showmessage(inttostr(v)); //mark line
Memo1.Lines.Add(strpas(s));
end;
when i delete the mark line(showmessage(inttostr(v))),i will have a correct result display in the Memo1,but if i keep use the showmessage(), the memo1 will dispaly an error string : "Messag" ,Why?
Thanks for any help!
function execProc(ProcName,InValues:PChar;out OutValues:PChar):integer;
var
str: TStrings;
InValue,OutValue: string;
i,j,scount: integer;
begin
Result := -100;
i := 0;
j := 0;
str := TStringList.Create;
try
sCount := ExtractStrings(['|'], [], InValues, str);
with kbmMWClientStoredProc1 do
begin
Close;
Params.Clear;
StoredProcName := StrPas(ProcName);
FieldDefs.Updated := False;
FieldDefs.Update;
for i := 0 to Params.Count - 1 do
begin
if (Params[i].ParamType = ptUnknown) or
(Params[i].ParamType = ptInput) or
(Params[i].ParamType = ptInputOutput) then
begin
inc(j);
InValue := str[j-1];
Params[i].Value := InValue;
end;
end;
try
ExecProc;
for i := 0 to Params.Count - 1 do
begin
if (Params[i].ParamType = ptOutput) or
(Params[i].ParamType = ptInputOutput) then
OutValue := OutValue + '|' + Params[i].AsString;
end;
OutValues := PChar(Copy(OutValue,2,Length(OutValue)-1));
Result := 0;
except
on E:Exception do
begin
if E.Message = 'Connection lost.' then Result := -101;//服务器连接失败
if E.Message = 'Authorization failed.' then Result := -102;//身份验证失败
Writelog(E.Message);
end;
end;
end;
finally
str.Free;
end;
end;
The problem is in the design of your interface and the use of PChar.
OutValues := PChar(Copy(OutValue,2,Length(OutValue)-1));
This is implemented by making an implicit, hidden, local string variable which holds the value
Copy(OutValue,2,Length(OutValue)-1)
When the function returns, that string variable is destroyed and so OutValues points at deallocated memory. Sometimes your program appears to work but that's really just down to chance. Any small change can disturb that, as you have observed.
The problem is easy enough to fix. Simply use string parameters rather than PChar. This will make the code easier to read as well as making it work correctly.
function execProc(ProcName, InValues: string; out OutValues: string): integer;
My table Customers has a field UserID which is indexed.
Now when I am dropping this Field from delphi, I am getting EOleExecption as its a indexed field.
I tried with following code:
ObjCustomers := TADOTable.Create(nil);
ObjCustomers.Connection := Connection;
ObjCustomers.TableName := 'Customers';
ObjCustomers.Open;
if (ObjCustomers.FindField('UserID').IsIndexField) then
begin
ExecuteSQLStatements(['DROP INDEX UserID ON Customers']);
end;
But this Tfield.IsIndexField is coming up False for this case.
Further I dont wanna do something like this:
try
ExecuteSQLStatements(['DROP INDEX UserID ON Customers']);
except
on E: exception do
end;
Is there any way so that I can check whether the field is Indexed, before executing SQL query?
Thankx in advance!
GetIsIndexField is not implemented by TADODataSet, and the result will be False.
Use TADOConnection.OpenSchema to retrieves table indexes:
var DataSet: TADODataSet;
DataSet := TADODataSet.Create(nil);
try
Connection.OpenSchema(siIndexes, VarArrayOf([Unassigned, Unassigned, Unassigned, Unassigned, 'Customers']), EmptyParam, DataSet);
while not DataSet.Eof do begin
ShowMessage(DataSet.FieldByName('INDEX_NAME').AsString);
DataSet.Next;
end;
finally
DataSet.Free;
end;
To make this answer complete:
As suggested by TLama you can use the TADODataSet method GetIndexNames.
ADO is internally using Command.ActiveConnection.OpenSchema(adSchemaIndexes...
function IsIndexField(DataSet: TADODataSet; FieldName: string): Boolean;
var
SL: TStringList;
begin
SL := TStringList.Create;
try
DataSet.GetIndexNames(SL);
Result := SL.IndexOf(FieldName) <> -1;
finally
SL.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
ObjCustomers: TADOTable;
begin
ObjCustomers := TADOTable.Create(nil);
ObjCustomers.Connection := Connection;
ObjCustomers.TableName := 'Customers';
if IsIndexField(TADODataSet(ObjCustomers), 'UserID') then
begin
Showmessage('Index');
Connection.Execute('DROP INDEX UserID ON Customers');
end
else
Showmessage('Not Index');
// ObjCustomers.Open;
ObjCustomers.Free;
end;
VAR
AdoTbl:TAdoDataset;
BEGIN
AdoTbl:=TAdoDataset.Create(Self); // use TAdoDataset
AdoTbl.Connection :=MyAdoConnection;
AdoTbl.CommandType:=cmdTable; //Importent !!
AdoTbl.CommandText:='Refx_Ceramics_Hist_PreHist'; //Tablename
AdoTbl.GetIndexNames(ListBox1.Items);
END;
This works for me on DelphiXE2
Using Delphi XE, TZipMaster 1.91 (latest)
I want to get the file name of first file name matching the FSpec arg that is passed to the Find function. However, I get an access violation when calling this function.
var
DBBakFl : String;
d : Integer;
begin
ZipMaster191.ZipFileName := 'myzip.zip';
try
DBBakFl := ZipMaster191.Find('*.FBK', d).FileName;
except
raise Exception.Create('Find raised an exception');
end;
Any help appreciated.
EDIT:
I sent an email got an almost immediate answer from one of the component's authors, Russell Peters.
See my answer below.
Try something like:
var
DBBakFl : String;
d : Integer;
DirEntry: TZMDirEntry;
begin
ZipMaster191.ZipFileName := 'myzip.zip';
DirEntry := ZipMaster191.Find('*.FBK', d);
if Assigned(DirEntry) then
begin
DBBakF1 := DirEntry.FileName;
....
end;
As find is failing, what you are effectively trying is to get the file name from a nil TZMDirEntry. Basically the same as:
var
DBBakFl : String;
DirEntry: TZMDirEntry;
begin
DirEntry := nil;
DBBakF1 := DirEntry.FileName;
end;
If the call to Find fails, you don't get back a valid TZMDirEntry instance, so you can't access the FileName property.
Try assigning the result of Find to a variable and check it for validity before trying to access its properties or methods. Maybe something like this. The documentation shows that TZMDirEntry is an abstract class, so you might need to use a descendent class instead.
var
DBBakFl : String;
d : Integer;
lDirEntry: TZMDirEntry;
begin
ZipMaster191.ZipFileName := 'myzip.zip';
lDirEntry := ZipMaster191.Find('*.FBK', d);
if Assigned(lDirEntry) then
DBBakFl := lDirEntry.FileName
else
ShowMessage('file not found');
I sent an email got an almost immediate answer from one of the component's authors, Russell Peters:
I am not surprise you get an AV
var
Idx: Integer;
Entry: TZMDirEntry;
DBBakFl : String;
begin
try
Idx := -1; // search from beginning, starts at Idx + 1
Index := ZipMaster191.Find('*.FBK', Idx);
if Index <> nil then
DBBakFl := Index .FileName;
except
raise Exception.Create('Find raised an exception');
end;
OR
var
Idx: Integer;
DBBakFl : String;
begin
try
Idx := -1; // search from beginning, starts at Idx + 1
if ZipMaster191.Find('*.FBK', Idx) <> nil then
DBBakFl := ZipMaster191[Idx].FileName;
except
raise Exception.Create('Find raised an exception');
end;
OR
var
Idx: Integer;
DBBakFl : String;
begin
try
Idx := -1; // search from beginning, starts at Idx + 1
ZipMaster191.Find('*.FBK', Idx) ;
if Idx >= 0 then
DBBakFl := ZipMaster191[Idx].FileName;
except
raise Exception.Create('Find raised an exception');
end;
In a loop it is easy
Idx := -1;
while ZipMaster191.Find('*.FBK', Idx) <> nil do
begin
DBBakFl := ZipMaster191[Idx].FileName;
Russell Peters