Datasnap - insert record with parameter - delphi

On DatasnapServer I have :
TSQLConnection connected to my database.
SQLDataset1 (CommandType=ctQuery) that fetches data from my table (lets call it "RESORTS".
DataSetProvider1 is connected to my SQLDataset1.
DataSetProvider1 is set to AllowCommandText.
This works OK. Server starts without a problem. Data is obtained.
On the Client side I have :
SQLConnection1 which is connected OK.Driver is Datasnap.
DSProviderConnection1 is linked to my SQLConnection1.
Both connected without problem.
Then I have DataSource1 which is connected to a ClientDataSet1.
ClientDataSet1 is connected to my DataSetProvider1.
Setting it active retrieves the data from the server.
All displayed right in the grid.
On the Client form I have a Edit1 and a Button1.
I try and run a query using the ClientDataset1
procedure TForm2.Button1Click(Sender: TObject);
begin
ClientDataSet1.Close;
ClientDataSet1.CommandText := ' INSERT INTO RESORTS (RES_NAME) VALUES (:RN)';
ClientDataSet1.FieldByName('RN').AsString := Edit1.Text;
ClientDataSet1.Execute;
ClientDataSet1.Open;
end;
I get : ClientDataSet1: Field 'RN' not found.
So, I am wondering what is going on? Why cant I insert data with parameter?
If I substitute the parameter with :
ClientDataSet1.CommandText := ' INSERT INTO RESORTS (RES_NAME) VALUES ("TRY")';
I get :Remote error: SQLDataSet1: Cursor not returned from Query.
However, the data does get inserted.
What am I doing wrong here ?

(Rewritten based on new information provided by the poster in comments.)
Your entire approach is wrong. :-) You don't use parameters, SQL or CommandText. The TClientDataSet.CommandText documentation clearly says:
CommandText specifies what data the client dataset wants to receive from its (internal or external) provider. It is either:
An SQL statement (query) for the database server to execute.
The name of a table or stored procedure
An SQL statement (query) means only a SELECT is acceptable SQL. An INSERT is not a query, so it cannot be used in a CommandText to insert data.
To insert data in a TClientDataSet, you simply Insert or Append, and then use FieldByName to set the value, and then call the Post method:
ClientDataSet1.Insert;
ClientDataSet1.FieldByName('RES_NAME').AsString := Edit1.Text;
ClientDataSet1.Post;
To edit, you simply use Edit instead of Insert or Append; the rest stays exactly the same.
ClientDataSet1.Edit;
ClientDataSet1.FieldByName('RES_NAME').AsString := Edit1.Text;
ClientDataSet1.Post;
When you're ready to actually update the server data from the changes made in the TClientDataSet, call it's ApplyUpdates:
ClientDataSet1.ApplyUpdates(0);

You can use ClientDataSet1.ParamByName('RN').AsString := Edit1.Text;

Related

How to get value of TDBLookupComboBox?

I have tables on my database Tb_barang and Tb_jenis. Tb_jenis the following columns kd_jenis (primary key) and jenis. I use TDBLookupComboBox to show the items from the Tb_jenis table. There are 6 items (Mie, Susu, etc). I want to save item selected as kd_jenis not jenis.
How to I save jenis in table to kd_jenis?
Sample data: Tb_jenis
jenis kd_jenis
Mie J1
Susu J2
Here it the code I've tried.
if (kd.Text='') or (jenis.Text='Pilih') then
ShowMessage('Data Tidak Lengkap, Silakkan Dilengkapi !')
else
begin
with dm.Qbarang do
begin
sql.Clear;
SQL.Add('select * from Tb_barang where kd_barang='+quotedstr(kd.text)+'');
open;
end;
if DM.Qbarang.Recordset.RecordCount > 0 then
ShowMessage('Data Sudah Ada, Silakkan Isi Yang Lain!')
else
begin
try
DM.koneksi.BeginTrans;
with DM.QU do
begin
close;
SQL.Clear;
SQL.Add('insert into Tb_barang values('+QuotedStr(kd.Text)+','
+QuotedStr(jenis.Text)+')');
ExecSQL;
end;
DM.koneksi.CommitTrans;
ShowMessage('Data Berhasil Disimpan');
except
DM.Qbarang.Close;
DM.Qbarang.Open;
FormShow(sender);
end;
end;
end;
As i understand you want to get Key Value of table from DBLookupComboBox.
so in it is 2 important property in DBLookupComboBox ListField and KeyField
if you set them correctly For Example KeyField set as kd_jenis and List Field Set as jenis then you will see Jenis on List and you can access to jenis in DBLookupCombobox.text and also you can access to KeyField (in this case kd_jenis) by DBLookupCombobox.KeyValue
you should fallow this steps.
Put a TADOConnection to your form for connecting to a database.
build connection string for connecting to Database.
add a ADO table to your form.
Set Connection property to AdoConnection1(Default Name Of ADOCOnnection).
On ADOTable open the Table name Property and select One of your database table and then set active property as true.
Add a DataSource to your form and set Dataset Property as ADOTable1(Default Name of ADOTable)
Add DBLookupCombobox to your form and Set ListSource as DataSource1(the default name of datasource)
Open ListField Property and Select witch field do you want to show in Combobox.
Open the keyField Property and Select witch field is Key field.
All of above Steps should done at Design time. for testing your application add to edit box on your form as edit1 and edit2 then write a small code for OnCloseUp Evwnt of DBLookupCombobox like this
Edit1.Text:=DBLookUpCombobox1.KeyValue;
Edit2.Text:=DBLookupCombobox1.Text;
If you have problems with DBLookupComboBox you may use normal combobox and fill it like the example here.
After someone selects something in your DBLookupComboBox...The cursor to the table is moved.
You can access the value directly:
jenis.ListSource.DataSet.FieldByName('kd_jenis').AsString
This assumes that jenis is a TDBLookupComboBox.
That ListSource.DataSet points to Tb_jenis.
As Craig stated...you have a serious bug in your code...you need a RollbackTrans on exception...other wise you will never release the lock...
DM.koneksi.CommitTrans;
ShowMessage('Data Berhasil Disimpan');
except
DM.koneksi.RollbackTrans;
DM.Qbarang.Close;
DM.Qbarang.Open;
FormShow(sender);
end;
I do something like this...if I need to guarantee saving info and rolling back if fails.
procedure TForm8.SaveData;
begin
Assert(not ADOQuery1.Connection.InTransaction, 'Code problem-We should not be in a Transaction');
ADOQuery1.Connection.BeginTrans;
try
ADOQuery1.ExecSQL;
ADOQuery1.Connection.CommitTrans;
finally
if ADOQuery1.Connection.InTransaction then
begin
{If you are here...your in an ErrorState...and didn't Commit your Transaction}
ADOQuery1.Connection.RollbackTrans;
HandleError(ADOQuery1);
end;
end;
end;

How to get the AUTOINC value when connecting via ADO?

I am trying to connect my database (Advantage 7.1 Server) using the Advantage OLE DB provider. So far so good...It connects with no problem with the code below:-
const
// the database we'll be connecting to
ConnectionString = 'Provider=Advantage OLE DB Provider;Data Source=C:\Data\'+
'UsersData.add;ServerType=ADS_REMOTE_SERVER|ADS_LOCAL_SERVER;User ID=ISUsers;Password=aAoO31';
My problem is, even though I'm able to connect to the database, any field with AUTOINC as the data type does not generate next numbers. "ID" as AUTOINC keeps on giving me zero (0) anytime I append the data instead of moving to the next number 1, 2, 3.... But for the same code if I switch to MS ACCESS, it works perfectly. What am I doing wrong? please find code below.
// Add template to database. Returns added template ID.
function TDBClass.addTemplate(template: TTemplate): Integer;
var
rs: TADODataSet;
tptStream: TMemoryStream;
id: Integer;
p: PChar;
begin
// get DB data and append one row
rs := TADODataSet.Create(nil);
rs.Connection := connection;
rs.CursorType := ctStatic;
rs.LockType := ltOptimistic;
rs.CommandText := 'SELECT * FROM enroll';
rs.Open();
rs.Append();
tptStream := TMemoryStream.Create();
// write template data to memory stream.
SafeArrayAccessData(template.tpt, Pointer(p));
tptStream.write(p^, template.size);
SafeArrayUnaccessData(template.tpt);
// save template data from memory stream to database.
(rs.FieldByName('template') as TBlobField).LoadFromStream(tptStream);
// update the database with added template.
rs.post();
// get the ID of enrolled template.
id := rs.FieldByName('ID').AsInteger;
// close connection
tptStream.Free();
rs.Close();
rs.Free();
addTemplate := id;
end;
You should consider using the TADSConnection, TADSQuery, etc. components that you can still download from the DevZone (http://devzone.advantagedatabase.com/dz/content.aspx?key=1) even for ADS 7.1.
If you have to use ADO, you probably have to use a different approach. (But see also bummi's comment about a possible bug in Delphi).
One way would be to use the LASTAUTOINC scalar function:
INSERT INTO
enroll
(
template
)
VALUES
(
:template
);
SELECT
LASTAUTOINC(CONNECTION) AS "id"
FROM
system.iota
In Delphi ADO you can pass more than 1 statement in your SQL query string - This might true for ADO in general.
Insert into test (text) Values('ddd'); Select * from test where AutoIncColumn = Scope_identity()
This will return the inserted row with the Autoinc value
or if you need just the autoinc value
Insert into test (text) Values('ddd'); Select Scope_identity() AutoIncColumn

Problem with using transaction in delphi when calling to MS SQL SERVER 2008 R2?

I need to call some Stored Procedures from Delphi and because they are related I have to use transactions.
But It always returns an error when called :
'Transaction cannot have multiple recordsets with this cursor type. Change the cursor type ,commit the transaction, or close one of the recordsets.'
And this error only occurs for MS SQL SERVER 2008, when I use MS Access It works fine.
Whats the problem ?
Thanks in advance
UPDATE :
procedure TForm1.Button2Click(Sender: TObject);
begin
if not DM.ADOConnection.InTransaction then
dm.ADOConnection.BeginTrans;
ADOQuery.LockType := ltBatchOptimistic;
ADOQuery.CursorType := ctUnspecified;
Try
with ADOQuery do
begin
Close;
SQL.Clear;
SQL.Text := 'INSERT INTO [UserAction] (UAct_Frm_ID,UAct_Type,UAct_Description'
+',UAct_Date,UAct_Time,UAct_Usr_ID)'
+'VALUES(:UAct_Frm_ID'
+',:UAct_Type,:UAct_Description,:UAct_Date,:UAct_Time'
+',:UAct_Usr_ID)';
Parameters.ParamByName('UAct_Frm_ID').Value := 1;
Parameters.ParamByName('UAct_Type').Value := 1;
Parameters.ParamByName('UAct_Description').Value := 'test by Q1';
Parameters.ParamByName('UAct_Date').Value := completdate(datenow);
Parameters.ParamByName('UAct_Time').Value := TimeToStr(Now);
Parameters.ParamByName('UAct_Usr_ID').Value := 1;
ExecSQL;
end;
Except
DM.ADOConnection.RollbackTrans;
ShowMessage('RollBack');
Exit;
End;
dm.ADOConnection.CommitTrans;
ShowMessage('Commite');
end;
From here:
Resolution:
Use a different cursor type, change
the cursor location to adUseClient or
close the first recordset before
opening another on the same
connection/transaction.
Cause:
SQL Server can only open one
ForwardOnly cursor at a time on a
connection, because SQL Server can
only process one active statement at a
time per connection.
When you try to open more than one
ForwardOnly ADO recordset at a time on
a single Connection, only the first
ADO recordset is actually opened on
the Connection object. New, separate
connections are created for subsequent
ForwardOnly cursors.
A transaction is on a single
connection. When you attempt to open
more than one ForwardOnly recordset
within a single transaction, ADO
attempts to open more than one
ForwardOnly recordset on the
connection of the transaction. An
error occurs because SQL Server only
allows one ForwardOnly recordset on a
single connection. Because the error
is within a manual transaction, you
might see the error above. Microsoft
Data Access Objects 2.1 Service Pack 2
and later versions of MDAC contain
more informative error messages. For
that reason, you may see the more
informative second or third error
message, above.
Try with including [eoExecuteNoRecords] into ExecuteOptions.

Everytime I try to communicate with my database with stored procedures I get this "Cannot perform this operation on a closed dataset"

I'm working on a Delphi project with a MS SQL Server database, I connected the database with ADOConnection, DataSource and ADOProc components from Borland Delphi 7 and I added this code in behind:
procedure TForm1.Button2Click(Sender: TObject);
begin
ADOStoredProc1.ProcedureName := 'sp_Delete_Clen';
ADOStoredProc1.Refresh;
ADOStoredProc1.Parameters.ParamByName('#clenID').Value := Edit6.Text;
ADOStoredProc1.Active := True;
ADOStoredProc1.ExecProc;
end;
The component Edit6 is an editbox that takes the ID of the tuple that should be deleted from the database and ADOStoredProc1 is the stored procedure in the database that takes 1 parametar (the ID you want to delete).
The project runs with no problems, I even got a TADOTable and a DBGrid that load the information from the database, but when I try to delete a tuple from the database using its ID written in the EditBox I get this Error: "Cannot perform this operation on a closed dataset" and the breakpoint of the project is when the application tries to add the value for the 'clenID' parameter.
Where is my mistake and how to fix it?
I think the ADOStoredProc1.Refresh method is not appropriate here. In this case the stored procedure does not return a result set. Could you leave it out? And also the line ADOStoredProc1.Active := True. The connection to the database is open I presume? Could you also check the values of the Parameters collection in the Object Inspector?
I think you want to call ADOStoredProc1.Parameters.Refresh, not ADOStoredProc1.Refresh.
Also, you should only set Active to True if the SQL Server Stored procedure returns a dataset - i.e. the result of a SELECT statement. Setting Active to True is the same as calling Open.
If the stored procedure only returns a result code (RETURN n), then use ExecProc.
In no case should you use both ADOStoredProc1.Active := True; and ADOStoredProc1.ExecProc;
In summary, you probably want something like
procedure TForm1.btnDeleteClick(Sender: TObject);
begin
ADOStoredProc1.ProcedureName := 'sp_Delete_Clen';
ADOStoredProc1.Parameters.Refresh; // gets the parameter list from SQL Server
ADOStoredProc1.Parameters.ParamByName('#clenID').Value := edtID.Text;
ADOStoredProc1.ExecProc;
end;

delphi Ado (mdb) update records

I´m trying to copy data from one master table and 2 more child tables. When I select one record in the master table I copy all the fields from that table for the other. (Table1 copy from ADOQuery the selected record)
procedure TForm1.copyButton7Click(Sender: TObject);
SQL.Clear;
SQL.Add('SELECT * from ADoquery');
SQL.Add('Where numeracao LIKE ''%'+NInterv.text);// locate record selected in Table1 NInterv.text)
Open;
// iniciate copy of record´s
begin
while not tableADoquery.Eof do
begin
Table1.Last;
Table1.Append;// how to append if necessary!!!!!!!!!!
Table1.Edit;
Table1.FieldByName('C').Value := ADoquery.FieldByName('C').Value;
Table1.FieldByName('client').Value := ADoquery.FieldByName('client').Value;
Table1.FieldByName('Cnpj_cpf').Value := ADoquery.FieldByName('Cnpj_cpf').Value;
table1.Post;
table2.next;///
end;
end;
//How can i update the TableChield,TableChield1 from TableChield_1 and TableChield_2 fields at the same time?
do the same for the child tables
TableChield <= TableChield_1
TableChield1 <= TableChield_2
thanks
The fields will all be updated at the same time. The actual update is performed when you call post (or not even then, it depends if the Batch Updates are on or off).
But please reconsider your logic. It would be far more efficient to use SQL statements (INSERT) in order to insert the data to the other table
SQL.Clear;
SQL.Add('INSERT INOT TABLE_1(C, client, Cnpj_cpf)');
SQL.Add('VALUES(:C, :client, :Cnpj_cpf)');
Then just fill the values in a loop.
SQL.Parameters.ParamByName('C').Value := ADoquery.FieldByName('C').Value;
SQL.Parameters.ParamByName('client').Value := ADoquery.FieldByName('client').Value;
SQL.Parameters.ParamByName('Cnpj_cpf').Value := ADoquery.FieldByName('Cnpj_cpf').Value;
SQL.ExecSQL;
You can also do the Updade - Insert pattern if the data can alredy be in the target table.
Like This:
if SQL.ExecSQL = 0 then
begin
// no records were update, do an insert
end;
And also the indication that you are copying data from table 1 to table 2 could be a sign of design flaw. But I can't say that for sure without knowing more. Anyway data duplication is never good.
I believe the asker was thinking about the data integrity, that means, ensure that only all the tables will updated or none...
The way I know to achieve this with security is executing all this updates (or inserts, a.s.o.) using SQL commands inside a transition.

Resources