TClientDataSet.Cancel Loses TDBMemo Values - delphi

I have a form with the following controls: TDBEdit, TDBMemo, TDataSource, TClientDataSet
If the user edits the fields and then clicks a button on the form that simply calls the MyCDS.Cancel method (to cancel the edits), the TDBEdit fields revert to their original values, but the TDBMemo fields are not reverted (they are set to blank values).
The TClientDataSet is populated from a MSSQLServer 2008 database. The TDBEdit fields are nvarchar(255) in the database, and the TDBMemo are nvarchar(max) or xml fields.
Looking at the values in the Debug Inspector (ctrl+F7, MyCDS.FieldByName('afield'), Inspect) shows the following for one of the nvarchar(max) fields:
DataSize = 0
DataType = ftWideMemo
Size = 1
This is the same for all of the nvarchar(max) and xml fields whether or not the underlying field has data or not.
It appears that there is an incompatibility between the nvarchar(max) (which is treated as a ftWideMemo) and the TDBMemo control.
Has anyone seen issues like this before? Do you have any suggestions how to resolve it?

In case anyone in interested, the problem is a bug in the TClientDataSet component. The problem only occurs when you clone a dataset, don't have a Provider and turn LogChanges off. If you edit a record on the cloned dataset and then cancel the edit, then any memo fields lose their values.
Since you don't necessarily use a Provider in file-based applications, it doesn't always make sense to enable LogChanges. However, to work around the limitation with memo fields, you can leave LogChanges on, and then call MergeChangeLog after operations that change the cloned dataset.
I created a program that demonstrates the issue. See the Embarcadero incident QC#110511.

Related

Delphi TDBGrid edit inplace with drop down list

I have to do maintenance to an old Delphi v7 application which uses a TDBGrid allowing inplace editting with drop down lists.
The issue is: the grid will show only the values present at data source. Without editting, this would be no problem at all. But if I change some value, the grid will only show the newly set value after updating the underlying register (until that the "old" value is displayed).
This only applies to drop down lists. Other input types (checkboxes, text edits) work without problems.
The edit itself also works fine: the changed values are actually reflected in the datasource when the register is UPDATE'd. My only problem is displaying this not-yet-UPDATE'd value.
I have no idea how to even debug this, or further inspect this behavior. So even if you don't have a complete answer, please indicate what else can I do to find a solution.
UPDATE
The project originally used TDB3DGrid (whose source I'm not aware of), but to simplify things I've changed it to TDBGrid. To no avail. The only change was that true/false columns are now displayed as text, and edited as text boxes (displaying and accepting "True" and "False" strings); with TDB3DGrid it showed checkboxes.
Other columns (not edited trough drop down list) are fine: after editing the new value is displayed correctly even before updating the underlying register.
I'm still tracking down where does the list of alternatives that fill the drop down list comes from.
Also still trying to come with an MCVE.
UPDATE 2
Apparently the (now) TDBGrid's affected TDBGridColumn has an empty PickList property. Sure for designtime. But I think this is not changed in runtime.
TDBGrid's source is a child of a TQuery, whose affected field has FieldKind equal to fkLookup. Other properties set for the field are FieldName, KeyFields, LookupDataSet, LookupKeyFields and LookupResultField.

How do I add data to a database in Delphi by using edits instead of a DBNavigator?

My interface is very basic. It just includes edits for the user to input data into a database, when they click the button i want it to add the data into my database.
You can easily do this.
Go to the Data Controls tab of the Component palette.
Select a TDBEdit and place it on the same form as your DBNavigator. The IDE will name this DBEdit1
Set the Datasource property of your DBEdit1 to the same datasource as your DBNavigator.
Set the DataField property of DBEdit1 to the name of a field in your dataset.
Compile and run.
That's it. Leave your DBNavigator on your form because you will find that when you make a change to the contents of DBEdit1, its Save and Cancel buttons automatically enable to let you save or cancel the change.
Also, you'll find that if you click your DBNavigator's '+' button, which begins the insertion of a new record into your table, you can then type the field values for the new record into your DBEdits.
Don't use normal non-DB-aware TEdit components and a dynamically-created Sql statement which concatenates the TEdits's contents with other Sql as suggested in the other answer which briefly appeared here and now seems to have been deleted - it is a waste of time, but much more importantly renders your app vulnerable to Sql-Injection - see https://en.wikipedia.org/wiki/SQL_injection. By sending the server an unverified Sql statement which includes what the user has typed into a TEdit, you're effectively providing the user with an opportunity to type additional Sql statements into the TEdit and that is exactly how Sql injection can occur. On the other hand, when you use TDBEdits, the Sql for updating the database record is automatically generated by Delphi's TDataSet framework in a way which does not provide a similar opportunity for Sql Injection.
If some reason you absolutely have to generate your own Sql Update statements, to minimise the risk of Sql Injection, make sure that you use a parameterised Update statement, that is, one where the changed field values are specified as values of parameters in your TDataSet-descendant's Parameters object, rather than in the Update Sql itself. An example of a parameterised Update statement might be:
Update MyTable set FieldA =:FieldA, FieldB=:FieldB where RowID =:RowID
where :FieldA, :FieldB and :RowID are the parameters.

How to add a non-bound column to a DevExpress DB QuantumGrid

I am using these components:
UniDac for connection to mysql database
DevExpress for QuantumGrid
IDE:
Embarcadero Rad Studio XE2
I have a cxGrid component with one level and a cxGrid1DBTableView specified as the level's View. I can get data from my database and edit it in the grid. I want to add a column that is not in the bound DataSet. When I specify the Column properties value as CheckBox I can see the column but I can't change the value from unchecked to checked by clicking it. The field doesn't have a DataBinding assigned to it. I tried other types of Properties but all are the same I cant change the row value in the grid.
I've been searching for a way to fix this for couple of days, so im hoping you guys can help me.
Are you trying to add a checkbox item that does not have a database field behind it? I have this on one of my forms.
In addition to setting the Properties to "Checkbox" you need to set the DataBinding -> ValueType to "Boolean". The DataBinding->FieldName can be left blank.
To access the values or change their defaults you can use the DataController like this:
View.DataController.Values[i, CheckBoxFieldIndex] := true;
In addition you need to set
DataController.DataModeController.SmartRefresh := true;
To set that option you will also need a KeyField defined for the Controller (DataController.KeyFieldNames)
Setting the SmartRefresh to true will prevent the grid from trying to get an updated value from the underlying dataset. You need to prevent the refresh or the value for the non-bound column will be set back to Null. This comes with some restriction on how you update your dataset. Any changes made to the data in code will not be reflected by the grid unless you explicitly refresh the grid.
You also have to fill the field View.DataController.KeyFieldNames with one of the datasets fields. At least I need it in Delphi 7.

How to retrieve information from a field that has the property Required set as false?

I have the following DBX structure in my software:
TSQLDataSet -> TDataSetProvider -> TClientDataSet
One of the fields from my TClientDataSet has the property Required set to false, because this field auto increments based on triggers and generators on the database (Firebird).
However, after configuring both TSQLDataSet and TClientDataSet with this field not being required, I'm getting really weird results when I try to read this field from my TClientDataSet. I suspect that I might need to do something extra to force my TClientDataSet to acquire the value of this field in this condition.
What am I missing here?
Thanks in advance.
EDIT
The help file for the Required property says something about this, but I couldn't quite understand what it want me to do.
Description
Specifies whether a nonblank value for a field is
required.
Use Required to find out if a field requires a value or if the field
can be blank.
If a field is created with the Fields editor, this property is set
based on the underlying table. Applications that set Required to true
for fields that must have values (for example, a password or part
number), but for which the underlying table does not require the
field, must write an OnValidate event handler to enforce the property.
When the Required property reflects a property of the underlying
database table, trying to post apply a null value causes an exception
to be raised. Applications that set the Required property to true when
the underlying table does not require the field, should raise an
EDatabaseError exception on null values in the OnValidate event
handler in order to achieve the same result.
EDIT 2
Forgot to mention: between the TDataSetProvider and the TClientDataSet, there is a DataSnap layer (the TClientDataSet connection is made with a DataSnap driver).
EDIT 3
I created a small test case with this DataSnap setup and it worked perfectly. The project is legacy, messy and I guess that either I have an obscure option configured somewhere that is biting me or I have stumbled in a DataSnap bug.
Haole, have you tried TClientDataset.RefreshRecord after inserting? Or even TClientDataset.Refresh?
Having generators, you can even get the generator in advance (before calling ApplyUpdates) in a query like select gen_id(generator,1) from RDB$Database (it's from memory, don't have Firebird here to test) and fill the PK field in advance.
EDIT: seems this is a heisenbug. I would try to remove the components and reconfigure them again from scratch (which means: after you remove, save and close Delphi).
Or even better, create an empty project with just that needed query configuration and try to view that data in a TDBGrid. If this problem still happens, maybe your FB installation have some component corrupted (or even Delphi installation)
Seems that the problem was an outdated field being read as an INTEGER and it was a SMALLINT in the database.
This problem was hard to debug and this question was misleading. Thanks for everyone that helped me debug this.

Delphi - ClientDataSet - validating data

I have an application in Delphi 7 which is using a clientdataset, and make several operations on it. ClientDataSet is linked to an Intraweb Grid.
I make an insert or an edit on the ClientDataSet. How can I verify the data introduced in the clientdataset for each field? I can not verify the input from the user on the webform, so I must make validation using ClientDataSet events.
LE: I want to validate data when the user make the input. Not at the onbeforepost event. So, I put the clientdataset in the edit/insert. User make an input in the grid, and I want to validate the data for that row in the clientdataset like in the image bellow:
first column is string, second is integer, the third one is also an integer. Now, I want to validate the third column after the user make the input. This validation must be done (if it is possible), only by using clientdataset events/hacks.
You should handle TClientDataset BeforePost event, and if data is not valid use abort method
TField has an OnValidate event for that purpose. It has also a CustomConstrain property that can use a SQL like syntax for constraints. DefaultExpression will let you select a value if no value is given. These are usuful for single-field validation. If you need more complex checks across more than one field, then you have to use dataset or datasource events.
Anyway, if the Intraweb grid sends the server whole records and not single fields edits, you may not validate fields as they are entered in the grid but adding code client side.
If you want a field based validation, you can handle the OnDataChange event of the TDatasource connecting the grid to the dataset. This will be triggered whenever a field has been changed by the user. You should be aware that it will also be triggered in some other situations.

Resources