Delphi ClientDataset Read-only - delphi

I am currently testing with:
A SQLConnection which is pointed towards an IB database.
A SQLDataset that has a SQLConnection field set to the one above.
A DatasetProvider that has the SQLDataset in (2) as its Dataset field value.
A ClientDataset, with the ProviderName field pointing to the provider in (3).
I use the following method (borrowed from Alister Christie) to get the data...
function TForm1.GetCurrEmployee(const IEmployeeID: integer): OleVariant;
const
SQLSELEMP = 'SELECT E.* FROM EMPLOYEE E WHERE E.EMPLOYEEID = %s';
begin
MainDM.SQLDataset1.CommandText := Format(SQLSELEMP, [Edit1.Text]);
Result := MainDM.DataSetProvider1.Data;
end;
Which populates the DBGrid with just one record. However, when I manually edit the record, click on Post, then try to commit the changes, using
MainDM.ClientDataset1.ApplyUpdates(0); // <<<<<<
It bombs, with the message "SQLDataset1: Cannot modify a read-only dataset."
I have checked the ReadOnly property of the Provider, and of the ClientDataset, and the SQL has no joins.
What could be causing the error?

It appears that your ClientDataSet.Data property is being populated from the Data property of the DataSetProvider. With the setup you described, you should be able to simply call ClientDataSet.Open, which will get the data from the DataSetProvider.
BTW, the default behavior of the DataSetProvider when you call the ClientDataSet.ApplyUpdates method is to send a SQL query to the connection object, and not the DataSet from which the data was obtained (assuming a homogeneous query). Make sure that your DataSetProvider.ResolveToDataSet property is not set to true.
Finally, on an unrelated note, your code above appears to be open to a SQL injection attack (though I have not tested this). It is safer to use a parameter to define the WHERE clause. If someone enters the following into Edit1 you might be in trouble (assuming the InterBase uses the drop table syntax): 1;drop table employee;

Check the LiveMode property of the TIBDataSet.

Related

How to insert value (data) to a field reference by ID of the specific row (record) in the delphi clientdataset

I am trying to insert a data to a field referenced by specific ID of row (or record) in the clientdataset at runtime.
I am using delphi and here's the structure of my case — mysql database > mysqluniprovider > uniquery > dataset provider > clientdataset > datasource > dbgrid.
The data I am trying to insert is generated during runtime by another code in the same procedure. Hence, dbnavigator will not work for me here. On the otherhand, I prefer to do this at the clientdataset level and do not want to direct to sql.
I was able to find the reference id by using the clientdataset.lookup/locate/findkey. But I could not be able to direct the cursor to the cell of the same row of reference id and specific field to insert the data.
I believed there must be a code component for this type of case just like cds.lookup/locate/findkey to update data in an existing table at runtime.
I will greatly appreciate any help on this.
The fact that you are using a TDBGrid to display the data is incidental, you change the data in the clientdataset and the TDBGrid will automatically update its display of the record. What you neeed to do is to use methods of of the clientdataset to move to the record and update its field data. You can use the clientdataset's Locate method to move to the record you want, as in:
ID := 99; // the ID of the record to change
if ClientDataSet1.Locate('ID', ID, []) then begin
ClientDataSet1.Edit; // Put the CDS into dsEdit mode so you can change its field data
ClientDataSet1.FieldByName('SomeField').AsString := 'Whatever';
ClientDataSet1.Post; // save the change(s) to the record
end;
See the online help for TField for its various AsXXX methods, such as AsInteger, AsFloat, etc.

Delphi and Firedac : query Active vs query Open

Most of my application's forms use 2 or more db grids : basically, the user clicks a record in main grid and get child results in a secondary grid.
All my primary DBGrids FDQueries (that is the one with SELECT) have been set on the form but none are "active", they fire on FormShow.
I noticed that, wether I write :
FDQuery1.Active := True;
or
FDQuery1.Open;
the result is the same : my rows are displayed in the main DBGrid.
Accordingly, I call Close or Active := False on FormClose.
But there must be a difference between those approaches and this is the subject of my question : what is the difference between Query.Open and Query.Active := True; ?
What should I use if there is any significant difference ?
Thanks in advance
Math, getting less and less noob as you take the time to answer my questions :)
SIDE NOTE : my INSERT and UPDATE queries are set up with a CLEAR then, SQL.ADD, then Parameters declaration and finally an ExecSQL
Active is a property, Open is a method. When Active is set to true, it calls the Open method, when it is set to false, it calls Close. Active is useful as it can be used to check whether the dataset is actually open.
if Qry.Active then DoSomething;
SIDE NOTE : my INSERT and UPDATE queries are set up with a CLEAR then,
SQL.ADD, then Parameters declaration and finally an ExecSQL
Between Active and Open is no difference.(see whosrdaddy comment) They do the same thing - The dataset becomes active and returns the result from SELECT statement.
You can also use property Active to check if the dataset is active for example:
if not MyQuery.Active then
MyQuery.Open; // or MyQuery.Active := true;
ExecSQL execute queries that do not return a cursor to data (such as INSERT, UPDATE, DELETE, and CREATE TABLE).

DataSnap using AutoInc key and refresh current record only after insert

I've not been able to find an answer on this anywhere. Using Delphi XE7 with TClientDataSet, DataSnap & SQL Server. I need to insert a record, apply updates and then refresh that record so I can get the Id and assign it to my object. Seems pretty basic requirement, but on the contrary it is proving to be a royal pain.
I've found the obvious stuff on EDN, SO and Dr Bob:
http://edn.embarcadero.com/article/20847
DataSnap and the autoinc field
http://www.drbob42.com/examines/examinC0.htm
However these seem to focus on a "Refresh" of the TClientDataSet to re-fetches the entire table/query. Whilst this does actually resolve the Id field itself (good!), it also moves the cursor off the current record which was just inserted and so I'm not able to get the Id and assign it to my object. Also, for performance over HTTP I don't really want to refetch the entire table every time a record is inserted, if there's 10,000 records this will consume too much bandwidth and be ridiculously slow!
Consider the following code:
function TRepository<I>.Insert(const AEntity: I): I;
begin
FDataSet.DisableControls;
try
FDataSet.Insert;
AssignEntityToDataSet(AEntity); // SET'S ALL THE RELEVANT FIELDS
FDataSet.Post;
FDataSet.ApplyUpdates(-1);
FDataSet.Refresh; // <--- I tried RefreshRecord here but it cannot resolve the record
AEntity.Id := FDataSet.FieldByName('Id').AsInteger; // <----- THIS NOW POINTS TO WRONG ROW
finally
FDataSet.EnableControls;
end;
end;
Does anyone know how to achieve this? I need to be able to refresh and stay on the current record otherwise I do not know the Id of the record just created and the GUI cannot stay focused on the current record.
Hopefully something obvious I'm missing.
Cheers.
Rick.
Assuming you can get hands on the new ID inside the AfterUpdateRecord event of your DataProvider, your event handler then may look like this (the current record of DeltaDS is the one just inserted into SourceDS):
if (UpdateKind = ukInsert) then begin
DeltaDS.FindField('Id').NewValue := <TheNewID>;
end;
Make sure to have the poPropogateChanges option set in the provider. This will transfer the changed Id field back to the ClientDataSet.
Now you can get rid of the FDataSet.Refresh call.
SQL Server does allow you to get the last identity it generated in several ways - there's no need to "refresh" the record/query which means re-issuing a SELECT and can generate undesiderable side-effects. You can use SELECT SCOPE_IDENTITY() or use an OUTPUT clause. If the Delphi database driver supports it, TField.AutogenerateValue should accomplish that task automatically (see http://docwiki.embarcadero.com/Libraries/XE7/en/Data.DB.TField.AutoGenerateValue)
Otherwise you have to put that new data into your delta (see Raabe answer - this has to be done on the datasnap server which actually talks to the database) after reading it, so it's sent back to the client. You also need to set properly and TField.ProviderFlags to ensure data are applied correctly (see http://docwiki.embarcadero.com/RADStudio/XE7/en/Influencing_How_Updates_Are_Applied), usually you don't want those field appear in an UPDATE.

Capability error while binding delphi grid wirth stored procedure

I am getting capability error whenever I am trying to set active property tue. I want to bind the grid with the stored procedure that takes one parameter.
If I do it through TQuery How do I specify the fields. I am using wwDBGrid.
MessageMembershipSelectQuery.Params[0].AsString :=
custQuery.FieldByName('cust_code').AsString;
MessageMembershipSelectQuery.Active := True;
Please guide
Replace you Query component by a StoredProc component. Also, check your parameter data type. If it is indeed a string parameter, then it´s Ok to use AsString, but if the parameter has a different data type, like Integer, for instance, then you should assign it´s value by using, for instance, AsInteger.

How to pass a parameter to a query using dbExpress in Delphi

I want to use a dbExpress TSQLQuery component. But i do not know how to write the SQL to add a parameter. I will give an example maybe it will be more clear what my problem is.
In a TADOQuery the following works:
SELECT*
FROM sometable
WHERE sometable.id = :value;
Now in the above example you pass the parameter to the query using the colon (:) before the parameter name. But when I try and do that with the TSQLQuery, I get the following error:
dbExpress driver does not support the TDBXTypes.UNKNOWN data type. Vendor Error Message.
Now if this is not the way that you pass a parameter in a TSQLQuery component, can someone please assist me. This is new territory for me.
Im using a Firebird database, and Im using Delphi XE2
To set the properties of a parameter you must use the Params property. From here you can access each paramer using a index or the name, to set the value of the parameter use one of the properties AsString, AsInteger an so on, depeding of the type of the field.
Check this sample
var
LSQLQuery : TSQLQuery;
begin
LSQLQuery:=TSQLQuery.Create(nil);
try
LSQLQuery.SQLConnection:=SQLConnection1;
LSQLQuery.CommandText:='Select FIRST_NAME from EMPLOYEE Where EMP_NO=:Param1';
LSQLQuery.Params.ParamByName('Param1').AsInteger:=2;//or using the index of the parameter LSQLQuery.Params[0].AsInteger:=2;
LSQLQuery.Open;//Execute the query
ShowMessage(LSQLQuery.FieldByName('FIRST_NAME').AsString); //get the data
LSQLQuery.Close;
finally
LSQLQuery.Free;
end;
end;

Resources