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

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.

Related

Max length TSQLConnection.Params values

Hello fellow StackOverflowers,
Currently I'm facing a situation where it seems that there is a maximum length for the Database property of a TSQLConnection object in Delphi.
When I open the connection to my database I get the following error when I use a rather long (154 chars) database name:
dbExpress Error: [0x0015]: Connection failed
SQL Server Error: unrecognized database parameter block
wrong version of database parameter block
When I relocate my database file to another location (and with that reduce the length of the path) it will connect to the database.
I am currently using the Object Inspector to set the connection properties of the TSQLConnection object.
Basically, my question comes down to this:
Does a TSQLConnection have a maximum length for the values set in the Params property? And if so, what is the maximum length of these values?
Update
I've found two ways to open a copy of Employee.Gdb in a folder with a 160-character name ('abcdefghij0123456789' x 8).
What I did firstly was to edit the DBXConnections.Ini file and changed the Database parameter in the [IBConnection] section to read
Database=localhost:D:\abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890\employee.gdb
Then, I can successfully connect to it, open the Employee.Gdb and make changes to the Customer table. I have verified the changes in IBConsole just in case the copy of Employee.Gdb wasn't the one I assumed it was.
Subsequently, I've found that I can create and open the db in code using Delphi Seattle and Interbase XE7, as follows:
function LongPath : String;
begin
Result := 'D:\' + DupeString('abcdefghij0123456789', 8);
end;
function LongDBName : String;
begin
Result := LongPath + '\Employee.Gdb';
end;
procedure TForm1.OpenDB;
var
Ini : TMemIniFile;
const
scDBXConIni = 'C:\Users\Public\Documents\Embarcadero\Studio\dbExpress\17.0\dbxconnections.ini';
scSourceDB = 'D:\Delphi\Databases\Interbase\Employee.Gdb';
begin
Ini := TMemIniFile.Create(scDBXConIni);
try
// First, blank out the Database value in the IBConnection section
// of DBXConnections.Ini
Ini.WriteString('IBConnection', 'Database', '');
Ini.UpdateFile;
// Next, create the long-named directory and copy Employee.Gdb to it
if not DirectoryExists(LongPath) then
MkDir(LongPath);
Assert(CopyFile(PChar(scSourceDB), PChar(LongDBName), False));
// Set LoadParamsOnConnect to False so that the SqlConnection uses
// the value of the Database we are about to give it
SqlConnection1.LoadParamsOnConnect := False;
SqlConnection1.Params.Values['Database'] := LongDBName;
SqlConnection1.Connected := True;
// Open the CDS to view the data
CDS1.Open;
finally
Ini.Free;
end;
end;
The critical step in doing it this way is setting LoadParamsOnConnect to False, which I confess I'd overlooked in earlier attempts to get this code to work.
I've got some earlier versions of Delphi on this machine, so if you're not using Seattle and the above code doesn't work for you, tell me which one you are using and I'll see if I can try that.
**[Original answer]
Actually, I think that this may be an error occurring in one of the DBX DLLs.
I created a folder with a 160-character name, then copied the demo Employee.Gdb database into it. Interbase XE7's IBConsole can open the db without error. So could a small test project contructed with IBX components in Delphi Seattle.
However, with an equivalent DBX project, when I use the code below
procedure TForm1.Button1Click(Sender: TObject);
begin
SqlConnection1.Params.Values['database'] := 'D:\abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890abcdefghij01234567890\employee.gdb';
SqlConnection1.Connected := True;
end;
I get an error in
procedure TDBXDynalinkConnection.DerivedOpen;
var
Count: TInt32;
Names: TWideStringArray;
Values: TWideStringArray;
IsolationLevel: Longint;
DBXError: TDBXErrorCode;
begin
Count := FConnectionProperties.Properties.Count;
FConnectionProperties.GetLists(Names, Values);
CheckResult(FMethodTable.FDBXConnection_Connect(FConnectionHandle, Count, Names, Values));
DBXError := FMethodTable.FDBXConnection_GetIsolation(FConnectionHandle, IsolationLevel);
'I/O error for file "database.gdb"
Error while trying to open file
The operation completed successfully'
and the Database param of the SqlConnection is left at the value 'Database.Gdb', which is not the value I specified, of course, nor was it the value specified in the params in the IDE, which was 'd:\delphi\databases\interbase\employee.gdb'.
I wondered if I could work around this problem by SUBSTing a drive to the 'abcdefg ...' path. I tried that and opening the database as "x:\employee.gdb" , but I get the same error in my DBX app, and also IBConsole cannot access the db either.
I think you need a shorter physical path!**
This is related to MSSql Server:
As a general guideline, long path names greater than 160 characters
might cause problems.
from Microsoft TechNet - https://technet.microsoft.com/en-us/library/ms165768(v=sql.105).aspx

Datasnap - insert record with parameter

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;

Getting Delphi 7 to play with SQL Server Compact 3.5

We have an old application that was written in Delphi 7. It is currently connected to an old Oracle Lite database that is being retired. The powers that be have chosen to move the data to a Microsoft SQL Server Compact database instead. After sepending a good amount of time moving everything over to the SQL CE database, I am now tasked with getting the Delphi application to play nice with the new databases.
The people who are supposed to be smarter than I am (my boss), tell me that I should be able to simply modify the connection and everything should be back in order. However, I have been banging my head against my monitor for two days trying to get the ADO connection in the Delphi application to work with our new SQL CE database.
A slightly simplified example of what I'm working with:
The connection is made in a global object with a TADOConnection named "adoConn":
procedure TGlobal.DataModuleCreate(Sender: TObject);
begin
adoConn.ConnectionString := 'Provider=Microsoft.SQLSERVER.CE.OLEDB.3.5;Data Source=path\db.sdf;';
adoConn.Connected := True;
end;
Shortly after this, a procedure is called to populate some messages. In an effort to trouble shoot the application, I've simplified the code to make a simple query and show the results in a message box. The procedure receives a parameter for the SQL string, but I'm ignoring it for now and manually inserting a simple select statement:
procedure Select(const SQL: string);
var
adoQuery : TADOQuery;
begin
adoQuery := TADOQuery.Create(nil);
try
adoQuery.Connection := Global.adoConn;
adoQuery.SQL.Text := 'select * from CLT_MESSAGES';
adoQuery.ExecSQL;
While not adoQuery.Eof do
begin
// Here I just created a MessageDlg to output a couple of fields.
adoQuery.Next;
end;
finally
adoQuery.Free;
end;
end;
Everything compiles just fine, but when I run the application I get the following error:
"Multiple-step operation generated errors. Check each status value."
I've done some additional trouble-shooting and found that the error is happening at adoQuery.ExecSQL. I've tried several different versions of the connection string and a couple different ways of trying to query the data, but it all ends up the same. I either can't connect to the database or I get that stupid "Mutliple-step" error.
I appreciate, in advance, any assistance that can be offered.
Don't use ExecSQL for queries that return recordsets.
Set either the AdoQuery.Active property to True or use AdoQuery.Open to execute a SELECT statement.
UPDATE
After changing your code we see the real error which is DB_E_OBJECTOPEN.
UPDATE2
After digging deeper it seems that this is a known bug in the OLE DB provider and nvarchar fields bigger than 127 characters.
these references seem to confirm this:
SO: SQL Server Compact Edition 3.5 gives "Multiple-step operation generated errors" error for simple query
ref1: http://www.tech-archive.net/Archive/SQL-Server/microsoft.public.sqlserver.ce/2008-07/msg00019.html
ref2: https://forums.embarcadero.com/thread.jspa?messageID=474517
ref3: http://social.msdn.microsoft.com/Forums/en-US/sqlce/thread/48815888-d4ee-42dd-b712-2168639e973c
Changing the cursor type to server side solved the 127 char issue for me :)

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;

how to maintain database output in combo box or dbgrid after closing TAdoconnection

In this question my objective is to retrieve a database table content.
populate dbGrid, close connection.
If I use the following code, dbgrid or combobox are going to loose the information.
adoQry := TADOQuery.Create(self);
adoQry.Connection := adoConn;
adoQry.SQL.Add(SqlStr);
adoQry.Prepared := true;
try
adoQry.Active := True;
except
on e: EADOError do
begin
MessageDlg('Error while doing query', mtError,
[mbOK], 0);
Exit;
end;
end;
for i := 0 to adoQry.RecordCount - 1 do
begin
cmbCnty.Items.Add(adoQry.Fields[1].AsString);
adoQry.Next
end;
FreeAndNil(adoConn);
FreeAndNil(adoQry);
In case dbGrid, I use StringGrid and it works for me.
However, sometimes I would like to use dbGrid, but not sure how to keep a content with the close connection to the database (after retrieving the content, of course)
Any suggestions, examples would appreciated.
Chris
You can populate a TClientDataSet with your Query ResultSet, and then link the TClientDataSet to the TDBGrid.
If I remember correctly (it a long time agoo I used it, and i can't try it from here), you can set the connection on til adoQuery to nil, and then it behaves like a disconnected dataset (like t-client dataset).
But the data goes away if you close the dataset (adqQry).
In the comment it was stated that the above statement is incorrect (and I still haven't tested it).
But in this Microsoft Knowlegde Base article "How To Create ADO Disconnected Recordsets" http://support.microsoft.com/kb/184397, it shows the same technique.
The same technique is also described in the Delphi about article "TOP ADO programming TIPS" http://delphi.about.com/od/beginners/l/aa021202a.htm
It it correct that setteing connection to nil in most dataset, also closes the dataset.
Benny is correct. You may have to use an TAdoDataSet instead of a TAdoQuery. TAdoDatasets and TClientDatasets have similar functionality. From my understanding, the TAdoQuery components were designed to help migrate a BDE app to dbGo.

Resources