Cannot assign data to client dataset - delphi

I'm using TClientDataSet assigned with local data in my Delphi application to compare data between 2 tables located in 2 different databases. Things I'm using:
SpPlansQuery: TADOQuery – original data query
PPUQuery: TADOQuery – changed data query (uses different ADO connection than SpPlansQuery)
ComparisonDataSet: TClientDataSet – dataset showing only differences between 2 previous queries
I'm trying to fill ComparisonDataSet as follows (I heavily reduced my code to highlight problem):
procedure TComparisonSpPlanForm.RefillDataSet;
const
// IMPORTANT: check that fields count in next 2 lines would be the same
FieldsStr1 = 'Article_PPU;Contractor_S_PPU;Recipient_S_PPU;OrderNum_PPU;OrderNum2_PPU;OrdN_PPU;Title_PPU;Queue_PPU;KolSht_PPU;Weight1_PPU;Material_PPU;Drawing_PPU;Graph_PPU';
FieldsStr2 = 'Title_1;Title_3;Title_4;num;inum;onum;Title;QNum;num_of;weight;Title_2;Drawing;Graph';
var
Deleted: Boolean;
FieldValues2: Variant;
FieldValues1: Variant;
begin
ComparisonDataSet.DisableControls;
// clear ComparisonDataSet
if ComparisonDataSet.Active then
ComparisonDataSet.Close;
ComparisonDataSet.CreateDataSet;
// deleted records
SpPlansQuery.First;
while not SpPlansQuery.Eof do
begin
FieldValues1 := SpPlansQuery[ReplaceStr(FieldsStr1, '_PPU', '')];
Deleted := not PPUQuery.Locate('ID', Integer(SpPlansQuery['PPONREC']), []);
if Deleted then
begin
ComparisonDataSet.Append;
// next string throws exception and this is a big problem
ComparisonDataSet[ReplaceStr(FieldsStr1, '_PPU', '')] := FieldValues1;
ComparisonDataSet.Post;
end;
SpPlansQuery.Next;
end;
ComparisonDataSet.First;
ComparisonDataSet.EnableControls;
end;
As far you can guess, ComparisonDataSet contains fields named ARTICLE and ARTICLE_PPU and exception with message "Cannot convert variant type of (Null) into type (Integer)" occurs when I try to assign value to ComparisonDataSet['ARTICLE']. I know this because I tried assigning directly to that field but got the same result.
ARTICLE is a string field with length of 20 characters.
Can anyone point me how to assign values to fields in a TClientDataSet without getting errors?
As requested below, this is my field definition:
In ComparisonSpPlanUnit.dfm:
object ComparisonDataSet: TClientDataSet
Aggregates = <>
FieldDefs = <
...
item
Name = 'ARTICLE'
DataType = ftString
Size = 20
end>
...
object ComparisonDataSetARTICLE: TStringField
DisplayLabel = #1057#1090#1072#1090#1100#1103
FieldName = 'ARTICLE'
end
...
end
In ComparisonSpPlanUnit.pas:
ComparisonDataSetARTICLE: TStringField;

During debug I found that exception is raised deep inside source code of Data.DB module, inside TStringField.SetVarValue procedure, so it seemed a bug. But I was wrong: deeper debugging highlighted that exception is raised during calculation of auto-calculated fields.
So I had to change my other function:
procedure TComparisonSpPlanForm.ComparisonDataSetCalcFields(DataSet: TDataSet);
begin
// comparing to Null is essential!
if DataSet['Oper'] <> Null then
case DataSet['Oper'] of
0: DataSet['OperStr'] := 'insert';
1: DataSet['OperStr'] := 'update';
2: DataSet['OperStr'] := 'delete';
else
DataSet['OperStr'] := 'other';
end
else
DataSet['OperStr'] := 'other';
end;
and it works now.

Related

null converting to 0.00 delphi

I inherited a program that copies all the info from one DB table into a different DB. The program was written in delphi 7 i believe and was using IDAC. Once I got it I converted updated it to Delphi 10.1 and moved it over to use FireDac. The issue I am having is in the original table it has fields with null values. When I move it over to the other DB it converts it from a null to 0.00. In the original program this did not happen and I cannot find anything in the code to tell it to do this. Does anyone have any idea how to have it insert the null instead of converting it.
Somewhere in your (or FireDAC's) code, the field's value is being handled as an integer-type value.
You can avoid this behaviour by doing a field-by-field copy along the following lines:
var
SourceField,
DestField : TField;
i : Integer;
begin
[...]
for i := 0 to SourceTable.FieldCount - 1 do begin
SourceField := SourceTable.Fields[i];
DestField := DestTable.Fields[i];
if SourceField.IsNull then
DestField.Clear // Sets DestField to Null
else
DestField.Value := SourceField.Value;
end;
[...]
end;
This assumes that the source- and destination-tables have the same structure, of course and that the fields are all non-blob types. Any blob field needs to be copied by the field's calling LoadFromStream and SaveToStream methods.
Change all the references of (SomeField).Value to (SomeField).AsVariant in your code. Because in FireDAC .Value is converted to integer/float/string/... (as .AsInteger did in Delphi 7), so your null values are converted to 0.00.
#MartynA's code will now be :
var
SourceField,
DestField : TField;
i : Integer;
begin
[...]
for i := 0 to SourceTable.FieldCount - 1 do begin
SourceField := SourceTable.Fields[i];
DestField := DestTable.Fields[i];
DestField.AsVariant := SourceField.AsVariant;
end;
[...]
end;
And any further access to the fields values do it through the .AsVariant method, so null values are not read as 0.

TClientDataSet error executing with TParam of blob type (ftBlob)

We are using MSSQL 2012.
Trying to update Client photo with Stored Procedure
spui_SetClientPhoto
int ClientID
VarBinary(Max) Photo
Program runs fine with pure ADO:
ADO.ProcedureName:='spui_SetClientPhoto';
ADO.Parameters.CreateParameter('#ClientsID',ftInteger,pdInput,0,95075);
ADO.Parameters.CreateParameter('#Photo',ftBlob,pdInput,0,NULL);
ADO.Parameters[1].LoadFromFile('C:\Photo.png',ftBlob);
ADO.ExecProc;
But with CDS it cause an error:
Implicit Conversion from datatype Varchar(max) to Varbinary(max) is not allowed.
ADO.ProcedureName:='spui_SetClientPhoto';
cds.SetProvider(ADO);
cds.Params.CreateParam(ftInteger,'#ClientsID',ptInput).AsInteger:=95075;
cds.Params.CreateParam(ftBlob,'#Photo',ptInput).LoadFromFile('C:\Photo.png', ftBlob);
cds.Execute;
e.g. cannot run CDS with Parameters of BLOB type. Any solution for this?
The following works fine for me, with the Picture field type in the AdoQuery and
CDS set to ftGraphic, and the Stored Proc's DDL set to
CREATE PROCEDURE [dbo].[SetClientPhoto](#ClientID int, #Picture Image)
AS
BEGIN
SET NOCOUNT ON;
update table_2
set picture = #Picture
where
ID = #ClientID
END
Code
procedure TForm1.SavePictureViaStoredProc;
var
PrvCommandText,
PrvSql : String;
ID : Integer;
const
scTestImage = 'D:\TestPictures\TestBMP.BMP';
begin
// First, save the text of the AdoQuery's Sql and the CDS's CommandText
PrvCommandText := CDS1.CommandText;
PrvSql := AdoQuery1.SQL.Text;
// Save the iD of the row we want to use
ID := CDS1.FieldByName('ID').AsInteger;
try
// Allow CommandText changes on the DSP
DataSetProvider1.Options := DataSetProvider1.Options + [poAllowCommandText];
CDS1.Close;
// construct a Sql statement to invoke the Stored Proc
CDS1.CommandText := 'exec dbo.SetClientPhoto #ClientID = :' + IntToStr(ID) + ', #Picture = :Picture';
// Set up parameters
CDS1.Params.Clear;
CDS1.Params.CreateParam(ftInteger, '#ClientID', ptInput);
CDS1.Params.CreateParam(ftGraphic, '#Picture', ptInput);
CDS1.Params.ParamByName('#ClientID').Value := ID;
CDS1.Params.ParamByName('#Picture').LoadFromFile(scTestImage, ftGraphic);
AdoQuery1.Close;
AdoQuery1.SQL.Text := '';
CDS1.Execute; // This executes the stored proc
CDS1.Params.Clear;
finally
ADoQuery1.SQL.Text := PrvSql;
CDS1.CommandText := PrvCommandText;
CDS1.Open;
end;
end;
Note: I very rarely store images in databases, and have not yet managed to get this to work with .Jpg and .Png files. I vaguely recall that there is an extra step needed with storing those in a DB without getting a "Stream read error" or "Invalid image" exception, and I'll see if I can remind myself of it later.

Add a blank row, then populate using a dialog in Delphi/DataSnap/dbExpress

I'm planning to create a dialog that would create a blank record in a Firebird database, then populate the fields with values taken from text edit boxes in that dialog.
I am using the following:
dbExpress
DataSnap
Delphi XE2
Maybe it should go like this:
DataSource.DataSet.Append;
(call the dialog with the db text boxes pointing to DataSource)
if ModalResult = mrOK then
DataSource.DataSet.Post;
Any ideas? I'd like to know which components to use (ClientDataSet, or SQLQuery or SQLDataSet). I've no idea on how to go about this.
EDIT: Code formatting
In other words, you want to create a dialog with non-data aware controls. In order to do this, you need three TSQLQueries: one to retrieve data when entering the dialog (in case of editing the data), one to insert and one to update.
Here is some (edited) code from such a dialog which I wrote the other day. The parameter 'n' is the id of the tuple to be edited; its value will be -1 if I am inserting a new record.
Function TEditCashbox.Execute (n: longint): boolean;
var
q: TSqlQuery;
begin
if n = -1 then
begin
edDate.Text:= datetostr (date);
edAmount.text:= '0';
end
else with qGetCashbox do
begin
params[0].asinteger:= n;
open;
edDate.text:= fieldbyname ('curdate').asstring;
edAmount.text:= fieldbyname ('amount').asstring;
edDetails.text:= fieldbyname ('details').asstring;
close
end;
if showmodal = mrOK then
begin
if n = -1 then
begin
q:= qInsertCashbox;
q.ParamByName ('p0').asinteger:= dm.GenerateID ('cashbox')
end
else
begin
q:= qUpdateCashbox;
q.ParamByName ('p0').asinteger:= n
end;
with q do
begin
parambyname ('p1').asdate:= strtodate (edDate.text);
parambyname ('p2').asinteger:= strtoint (edAmount.Text);
parambyname ('p3').asstring:= edDetails.text;
execsql
end;
end
end.
qGetCashbox is a query defined as
select curdate, amount, details from cashbox where id = :p1
qInsertCashbox is
insert into cashbox (id, curdate, amount, details)
values (:p0, :p1, :p2, :p3)
qUpdateCashbox is
update cashbox set
curdate = :p1,
amount = :p2,
details = :p3
where id = :p0
Of course, you could also use data aware components, which require the 'trinity' - TSQLDataSet, TDataSetProvider and TClientDataSet. Using data aware components is easier, but sometimes there are cases in which this approach is not suitable. If you do use data aware components, then my template code is as follows
sdsEditDeposit.params[0].AsInteger:= n; // this is the TSQLDataSet
with qEditDeposit do // this is the clientdataset
begin
open;
if n = -1 then // new tuple
begin
insert;
fieldbyname ('amount').asinteger:= 0;
fieldbyname ('curdate').asdatetime:= date;
end;
edit;
if showmodal = mrOK then
begin
if n = -1 then
begin
n:= dm.GenerateID;
fieldbyname ('id').asinteger:= n;
end;
result:= n;
post;
applyupdates (0)
end
else
begin
cancel;
result:= 0
end;
end;
You might want to allow for a cancel as well...
DataSource.DataSet.Append;
(call the dialog with the db text boxes pointing to DataSource)
if ModalResult = mrOK then
DataSource.DataSet.Post
else
Datasource.Dataset.cancel;
I use TADOQuery components with MS-SQL and it works reliably.
You need a TSQLConnection configured for your database and (to make it easy) a TSQLTable. Link the TSQLTable to the TSQLConnection and select the desired table in the TableName property. Next drop a TDataSetProvider onto the form and connect it with the TSQLTable. Now take a TClientDataSet, set its Provider to the TDataSetProvider and connect the DataSource to the TClientDataSet.
Remember: to actually write the data into the database you have to call TClientDataSet.ApplyUpdates.
More info here

Need help with Delphi and ADOTable filtering

i m trying to build an invoice program that holds data in an Access db. i have some tedit s, buttons, one datasource, one adotable, one dbgrid and a popup menu. database format is accdb.
Problem: i want the program to filter records while user is typing. it might filter dbgrid or tedit, doesn t matter. i somehow found some code, for example:
Table1.FilterOptions:=[foCaseInsensitive];
Table1.Filter:='Filmadi='+QuotedStr(Edit1.Text+'*');
Table1.Filtered:=true;
the code above gives this error: Project Project1.exe raised exception class eoleexception with message: Item cannot be found in the collection corresponding to the requested name or ordinal
other examples give various errors.
sincerely
onur
Use LIKE operator in your filter:
procedure DoIncrementalFilter(Dataset: TDataSet; const FieldName, SearchTerm: string);
begin
Assert(Assigned(Dataset), 'No dataset is assigned');
if SearchTerm = '' then
Dataset.Filtered := False
else
begin
Dataset.Filter := FieldName + ' LIKE ' + QuotedStr(SearchTerm + '*');
Dataset.Filtered := True;
end;
end;
Example:
DoIncrementalFilter(ADOTable1, 'Filmadi', Edit1.Text);

how to show Only relevant information in dbgrid delphi

information:
I have an order form.
With "keuze" and "aantal" it wright a new line. The Orderline gets an OrderID.
But the user may only see the orderline from his OrderID.
How can i make it work that it only shows, for example the OrderID "47" ?
procedure TfmOrder.btInvoerenClick(Sender: TObject);
begin
dm.atOrder.open;
dm.atOrder.Append;
dm.atOrder ['OrderStatus'] := ('Aangemeld');
dm.atOrder ['klantID'] := fminloggen.userid;
dm.atOrder ['OrderDatum'] := Kalender.date;
dm.atOrder ['Opmerkingen'] := leOpmerkingen.text;
dm.atOrder.post;
cbkeuze.Visible := true;
dbRegel.Visible := true;
leAantal.visible := true;
btOpslaan.Visible:= true;
end;
This is the code for making a new Order
procedure TfmOrder.btOpslaanClick(Sender: TObject);
var orderid:string;
begin
dm.atOrderregel.Open;
dm.atDier.open;
dm.atorderregel.Append;
dm.atOrderregel ['AantalDieren'] := leAantal.text;
dm.atOrderregel ['OrderID'] := dm.atOrder ['OrderID'];
dm.atOrderregel ['Diernaam'] := cbKeuze.Text;
dm.atOrderregel.Post;
leaantal.clear;
cbkeuze.ClearSelection;
end;
And this for a new orderline
thanks in advance
I know got a different error using this code:
begin
dm.atorder.Open;
dm.atorder.filter := 'KlantID = ' + (fminloggen.userid);
dm.atorder.filtered := true;
while not dm.atorder.Eof do
begin
cbOrder.Items.Add (dm.atorder['OrderID']);
dm.atOrder.Next;
end;
dm.atOrder.Close;
end;
It gives an error: The arguments are from the wrong type, or doesn't have right reach or are in conflict with each other.
here is userid declared.
var Gevonden: boolean;
userid : string;
begin
dm.atInlog.open;
Gevonden := false;
while (not Gevonden) and (not dm.atInlog.eof) do
begin
if dm.atInlog['email'] = leUser.Text
then
begin
Gevonden := true ;
inlognaam := dm.atInlog['email'];
userid := dm.atInlog['KlantID'];
end
else
dm.atInlog.Next
end;
this is obviously in another form
You can use the Filter property of the data set:
atOrderregel.Filter := 'OrderID = 47';
atOrderregel.Filtered := True;
You can add the grid's columns property statically in the object inspector, showing only the fields you need. If the columns list is empty (default) it is filled with all available fields.
Just add as many columns as you need and link each column to the corresponding field. You can reorder the columns and set the widths and titles individually. There are still some more properties available which are worth to explore.
Im assuming your grid is bound to a datasource component. This datasource is then linked with a TDataset descendant. There are a couple of ways you could acheive the desired filtering of the dataset to display only orderid 47.
Firstly, you could set the Datasets SQL property to contain a (server side) SQL query such as:
SELECT * from table WHERE OrderID = #OrderID
You would also need to create a parameter in the dataset to pass the (changing) value for the required OrderID. So add a new Parameter to the dataset (#OrderID), and then at runtime you can set this parameter value in code, something like:
DataSet.Parameters['#OrderID'].Value := ParameterValue;
Alternatively, you could also FILTER the dataset (client side) to just show the correct data:
Set your SQL property of the dataset to retrive the entire table, something like:
SELECT * FROM table
And then at runtime you could set the Filter property of the dataset to only get OrderID 47:
Dataset.Filter := 'OrderID = '+InttoStr(ParameterValue);
Depending on your needs one method may suit better (performance/memory) wise.
As Najem has commented, there is also a third method - using a Master-Detail dataset relationship. This method works using two datasets, one is the master of the other. When the master table record is changed, the detail dataset is then filtered using the value defined in the Key or MasterFields property of the M-D relatioship.
If you are connected to some datasource you could always create a SQL Query. Something like:
SELECT * FROM YourDBTable WHERE OrderID=47

Resources