How can i add fields to TfrxDBDataset - delphi

if Length(idStrArray)>0 then
begin
with DataModule4.ADQueryTemp do
begin
Close;
SQL.Clear;
SQL.Add('SELECT id, pato, ftest, res FROM tbl ');
SQL.Add('WHERE id IN ('+idStrArray+')');
Open;
(rprMasterDataFish as Tfrxmasterdata).DataSet := frxDst_Multi;
(rprMasterDataFish as Tfrxmasterdata).DataSetName := 'Multi';
end;
end;
Hello,
I have TfrxDBDataset component. I can add fields from table like above. But i also want to add fields and values manually at runtime.
I have text file like this :
id note
1 sample
2 sample
I want to read this text file and insert note to frxDst_Multi. Is this possible ?
I dont want to create a new column as note in tbl. Because, i have too many mysql server.
Thanks in advice,

You can't add fields to a dataset while it is open, so you have to do it before
it is opened, either in code or using the TDataSet fields editor. If you are
doing it in code, you can add the field in the dataset's BeforeOpen
event.
The next problem is that is you don't want to field to be bound to the table the
dataset accesses, you need to add it as a calculated field and set its value
in the dataset's`OnCalcFields' event - see example below.
Ideally, the added field would be a TMemoField, but unfortunately a TMemoField
can't be a calculated field (FieldKind = ftMemo). So probably the best thing you can do is to make it a String field, but then you will need to
give it a fixed maximum size and truncate the field's value at that size when you calculate its value.
Btw, I don't know whether your TfrxDBDataset supports the fkInternalCalc fieldkind, but if it does, then you could try adding the note field as a TMemoField instead of a TStringField one.
The one thing I haven't been able to do is to load the field's value from
an external file because you haven't said in your q how to determine the
name of the file which is to be read.
Obviously, the IsNull check in the frxDst_MultiCalcFields event is to avoid the overhead of reloading the file if its contents have already been read.
const
NoteFieldSize = 4096;
procedure TForm1.AddNoteField;
var
NoteField : TField;
begin
if frxDst_Multi.FindField('Note') = Nil then begin
NoteField := TStringField.Create(frxDst_Multi);
NoteField.FieldName := 'Note';
NoteField.Size := NoteFieldSize;
NoteField.FieldKind := fkCalculated;
NoteField.DataSet := frxDst_Multi;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
frxDst_Multi.Open;
end;
procedure TForm1.frxDst_MultiCalcFields(DataSet: TDataSet);
var
S : String;
begin
if DataSet.FieldByName('Note').IsNull then begin
S := 'a note'; // replace by code to set the field value
DataSet.FieldByName('Note').AsString := Copy(S, 1, NoteFieldSize);
end;
end;
procedure TForm1.frxDst_MultiBeforeOpen(DataSet: TDataSet);
begin
AddNoteField;
end;

Related

Get id from Values in a StringList

I have a StringList with 2 rows
aOldPriceTerms[0] = 'FuelAddition=336643160'
aOldPriceTerms[1] = 'Freight=336643155'
So it works fine to use
aOldPriceTerms.Values['Freight'] -> '336643155'
I want to have a list of ID's from the list.
So simply
'FuelAddition','Freight'
Currently I use this code where aOldPriceTerms is the actual StringList.
function GetList(aOldPriceTerms: TStringList): String;
var
vOldTerm : string;
vOldTermsList : TStringList;
begin
vOldTermsList := TStringList.Create;
try
for i := aOldPriceTerms.Count - 1 downto 0 do
begin
vOldTerm := Copy(aOldPriceTerms[i], 1, (Pos('=', aOldPriceTerms[i])-1));
vOldTermsList.Add(vOldTerm);
end;
Result := vOldTermsList.CommaText;
finally
FreeAndNil(vOldTermsList);
end;
end;
My question is there a cleaner way to get the ids ?
Example is from the Delphi Basics, but TStringList.Names is also described in the Delphi documentation
// Now display all name and age pair values
for i := 0 to names.Count-1 do
begin
ShowMessage(names.Names[i]+' is '+names.ValueFromIndex[i]);
end;
You can use the TALNVStringList (NV for nameValue) from alcinoe (https://github.com/Zeus64/alcinoe) to handle Name and Value without all the time splitting the string
TALNVStringList is exactly the same as TStringList (nothing to change in the code except replacing TstringList by TALNVStringList) except that it's more efficient in speed because it's store the name in one dedicated field and the value in another dedicated field (no need to do all the time pos('=') and copy() to retrieve the name and the value of the row)
for i := 0 to aOldPriceTerms.Count-1 do
begin
ShowMessage(aOldPriceTerms.Names[i]+' is '+aOldPriceTerms.ValueFromIndex[i]);
end;
Exe demo showing the speed penalty of classic TstringList: https://svn.code.sf.net/p/alcinoe/code/demos/ALSortedListBenchmark/win32/ALSortedListBenchmark.exe
To get all the id's
for name in names.Names do
begin
i := names.IndexOf[name];
end;
or
To get all the Values
for name in vOldTermsList.Names do
begin
Value := vOldTermsList.Value[name];
end;

obtain customer_id automatically on new record

I am pulling down records with a stored procedure of customers that have a customer_id=5.
Now the procedure gets the data and shows it in the grid and I want to add a new record .
How can I make sure that when I add new record in the grid it has automatically the same customer_id ( 5 ) ?
I dont want to write it down all the time.I used to know this but I forgot.The DB is sql server.
You can set the DefaultExpression to the used ID for the field you want. For example for a UniDAC stored procedure object you can do (ParamCustID is the procedure input parameter, FieldCustID is the returned dataset field name):
var
CustID: Integer;
begin
CustID := 123;
UniStoredProc1.StoredProcName := 'MyProcedure';
UniStoredProc1.PrepareSQL;
UniStoredProc1.ParamByName('ParamCustID').AsInteger := CustID;
UniStoredProc1.Execute;
UniStoredProc1.FieldByName('FieldCustID').ReadOnly := True;
UniStoredProc1.FieldByName('FieldCustID').DefaultExpression := IntToStr(CustID);
end;
The above code will setup the field for assigning same value for every appended tuple, and makes it read only. Out of curiosity, the same task for FireDAC would go like this (for applying changes, there must be an explicit method call for updating field attributes):
var
CustID: Integer;
begin
CustID := 123;
FDStoredProc1.StoredProcName := 'MyProcedure';
FDStoredProc1.Prepare;
FDStoredProc1.ParamByName('#ParamCustID').AsInteger := CustID;
FDStoredProc1.Open;
FDStoredProc1.FieldByName('FieldCustID').ReadOnly := True;
FDStoredProc1.FieldByName('FieldCustID').DefaultExpression := IntToStr(CustID);
FDStoredProc1.UpdateAttributes;
end;

Get current field in TQuery to a TField

This is related to another question but doesn't really fit enough to include it with the original. When a Post is called, how can I get the field (or fields) that was modified to a TField?
For logging, I use the OnBeforePost event, which is called (as it says) just before the data is posted. The drawback to this, of course, is that your log table has to have fields wide enough to hold all possible content.
procedure TMyData.SomeTableBeforePost(DataSet: TDataSet);
var
i: Integer;
begin
for i := 0 to DataSet.FieldCount - 1 do
begin
// Skip calculated and lookup fields
if DataSet.Fields[i].FieldType = ftData then
begin
if DataSet.Fields[i].OldValue <> DataSet.Fields[i].NewValue then
begin
LogTable.Insert;
LogTableColumnName.AsString := DataSet.Fields[i].FieldName;
LogTableOldValue.Value := DataSet.Fields[i].OldValue;
LogTableNewValue.Value := DataSet.Fields[i].NewValue;
LogTable.Post;
end;
end;
end;
end;

How do you log a specific table in Accuracer?

Code like this logs all table inserts (from the entire application):
procedure TForm1.ACRDatabase1AfterInsertRecord(Sender: TACRDataSet;
const TableName: WideString; const FieldValues: TACRArrayOfTACRVariant);
begin
if (AnsiUpperCase(TableName) = AnsiUpperCase(LogTable.TableName)) then
Exit;
if (Sender is TACRTable) then
LogTable.Insert();
LogTable.FieldByName('EventTime').AsDateTime := Now;
LogTable.FieldByName('TableName').AsString := TableName;
LogTable.FieldByName('EventType').AsString := 'Insert ';
LogTable.FieldByName('Whatever').AsString := FieldValues[4].AsString;
LogTable.Post();
end;
But fieldValues are different for each table so you might crash
the application (almost sure) using fieldvalues i.e their index number.
How do you overcome this ? Is it possible to log each table separately ?
As I mentioned in a comment, I don't have Accuracer, but thought it might be helpful
to post a generic method of doing client-side logging, which can capture the value
of one or more fields and be used for as many datasets as you need. You may be
able to use part of it in your ACRDatabase1AfterInsertRecord handler, as its Sender
parameter appears to identify the dataset into which the new row has been inserted.
As you can see, there is a LogFields procedure which can be included in the AfterInsert
handler of any dataset you like and this calls a separate GetFieldsToLog procedure which
adds the names of the field(s) to log for a given dataset to a temporary StringList. It's
only the GetFieldsToLog procedure which needs to be adapted to the needs of a given set of datasets.
procedure TForm1.GetFieldsToLog(ADataSet : TDataSet; FieldList : TStrings);
begin
FieldList.Clear;
if ADataSet = AdoQuery1 then begin
FieldList.Add(ADataSet.Fields[0].FieldName);
end
else
// obviously, deal with other specific tables here
end;
procedure TForm1.LogFields(ADataSet : TDataSet);
var
TL : TStringList;
i : Integer;
ValueToLog : String;
begin
TL := TStringList.Create;
try
GetFieldsToLog(ADataSet, TL);
for i := 0 to TL.Count - 1 do begin
ValueToLog := ADataSet.FieldByName(TL[i]).AsString;
// do your logging here however you want
end;
finally
TL.Free;
end;
end;
procedure TForm1.ADOQuery1AfterInsert(DataSet: TDataSet);
begin
LogFields(DataSet);
end;
Btw, one of the points of having a separate GetFieldsToLog procedure is that it helps to extend
client-side logging to changes in existing dataset records. If you generate this list
at start-up and save it somewhere, you can use it in the BeforePost event of a dataset to
pick up the current and previous values of the field (using its Value and OldValue properties),
save those in an another StringList and log them in the AfterPost event. Of course,
if you'e using a common store for these value from more than one dataset, you need to make
sure that the AfterPost of one dataset fire before the BeforePost of any other, or do the logging
entirely within the BeforePost (having to store the old and current field values between
Before- and AfterPost is messy, and it would be better to do everything in the AfterPost,
but unfortunately the OldValue is out-of-date by the time AfterPost occurs.
Be aware that getting the OldValue requires the specific dataset type to correctly implement
it. Not all types of dataset I've come across do, though, so it needs checking.
Btw #2, supposing you have a procedure like this
procedure TForm1.DoSomething(AnObject : TObject);
then you can use "if AnObject is ..." to do something like this
var
AnAdoQuery : TAdoQuery;
begin
if AnObject is TAdoQuery then begin
// First, use a cast to assign Sender to the local AnAdoQuery variable
AnAdoQuery := TAdoQuery(AnObject);
// Then, we can do whatever we like with it, e.g.
Caption := AnAdoQuery.Name;
end;
end;
Otoh, if for some reason (and I can't immediately think why we would want to but never mind)
we just want to check that what we've been passed as the AnObject parameter is a particular
object, we can omit the cast and just do
if AnObject = AdoQuery1 then
ShowMessage('Received AdoQuery1');
This equality check works, regardless of the actual class of what we've been passed
as the AnObject parameter because all other classes are descendants of AnObject's
declared class, namely TObject.

How to find the "foreign key" field name on a nested TClientDataSet?

Given a nested TClientDataSet, how could I find the link field name on the detail TClientDataSet?
I'm copying data from one TClientDataSet to another (record by record) and I would like to automatically ignore the link field.
I could also copy the data using the TClientDataSet.Data property but I still would need to clear the link and key fields.
You can check the "DataSetField" property of the detail/nested dataset, or access the "NestedDataSet" property of the master dataset.
Sample code to get the "link" field name:
function GetCDSDLinkFieldName(cds: TClientDataSet): string;
var
i: Integer;
cdsDetail: TClientDataSet;
begin
Result := EmptyStr;
cdsDetail := nil;
if Assigned(cds.DataSetField) then
cdsDetail := cds;
if not Assigned(cdsDetail) and (cds.FieldCount > 0) then
begin
i := 0;
while not Assigned(cdsDetail) and (i < cds.FieldCount) do
begin
if cds.Fields[i].DataType = ftDataSet then
cdsDetail := TClientDataSet(TDataSetField(cds.Fields[i]).NestedDataSet);
Inc(i);
end;
end;
if Assigned(cdsDetail) then
Result := cdsDetail.DataSetField.FieldName;
end;
Invoking example:
procedure ...
begin
ShowMessage(GetCDSDLinkFieldName(cdsMaster));
ShowMessage(GetCDSDLinkFieldName(cdsDetail));
end;
P.S.: 2 years later I don't believe this answer will help the author of the question, but maybe can help others that search for the same subject.

Resources