Reported Problem:
exception class : AdsError
exception message : Error 5047: The transaction command was not in valid sequence. Not in a transaction.
main thread ($918):
00607cc5 +07d Boxer.exe adscnnct 1576 +20 TAdsConnection.PerformRollback
00607d52 +002 Boxer.exe adscnnct 1606 +0 TAdsConnection.Rollback
0062eab5 +09d Boxer.exe DModule1 241 +10 TDM1.ApplyBoxer2Changes
Offending code:
procedure TDM1.ApplyBoxer2Changes;
begin
AdsConnection.BeginTransaction;
try
if cdsBoxes.ApplyUpdates(0) = 0 then
AdsConnection.Commit
else
raise Exception.Create('Record changed by another user.');
except
on e: Exception do
begin
AdsConnection.Rollback;
MessageDlg('An error occurred while attempting to save your changes:'+#13#10#10
+e.Message, mtError, [mbOK], 0);
cdsBoxes.CancelUpdates;
end;
end;
cdsBoxes.Refresh;
end;
Where cdsBoxes is a TClientDataSet connected to a TDataSetProvider whose DataSet is a TAdsQuery with the same AdsConnection = TDM1.AdsConnection, and the following SQL:
SELECT * FROM Boxer2 WHERE Boxer1Id=:id
Can anyone see what is wrong with this code?
The 5047 error appears to be indicating that the transaction is no longer active when the Rollback method is called.
I haven't been able to reproduce the error myself; in my testing I'm able to add, edit and delete multiple records and then all the changes are either committed or the rollback occurs successfully.
Update: I have been able to reproduce it. The problem occurs when there are 2 identical records in the table, so it is impossible to identify which one to update (there is no unique key on the table, DataSetProvider.UpdateMode = upWhereAll).
I'm not sure why the transaction is being cancelled automatically in that case, but a workaround to at least present a user-friendlier message is as follows:
procedure TDM1.ApplyBoxer2Changes;
begin
AdsConnection.BeginTransaction;
try
if cdsBoxes.ApplyUpdates(0) = 0 then
AdsConnection.Commit
else
raise Exception.Create('Record changed by another user.');
except
on e: Exception do
begin
if AdsConnection.TransactionActive then
begin
AdsConnection.Rollback;
MessageDlg('An error occurred while attempting to save your changes:'+#13#10#10
+e.Message, mtError, [mbOK], 0);
end
else
begin
MessageDlg('A data integrity error occurred while attempting to save your changes.'+#13#10#10
+'Please report this to the help desk.', mtError, [mbOK], 0);
end;
cdsBoxes.CancelUpdates;
end;
end;
cdsBoxes.Refresh;
end;
Related
I'm trying to capture the debugger notification text, Firedac connection
//FDScript1.SQLScripts.Add.SQL.LoadFromFile('C:\SGI\Rodar_Apos_Atualizar_3.txt');
//FDScript1.ValidateAll;
//FDScript1.ExecuteAll;
//MSScript1.SQL.LoadFromFile('C:\SGI\Rodar_Apos_Atualizar_3.txt');
//MSScript1.Execute;
try
FDConnection1.Connected := True;
FDScript1.SQLScripts.Add.SQL.LoadFromFile('C:\SGI\Rodar_Apos_Atualizar_3.txt');
FDScript1.ExecuteAll;
FDScript1.ValidateAll;
except
//EMSSQLNativeException
on E: EMSSQLNativeException do
begin
//ShowMessage('Erro'+FDGUIxErrorDialog1.ErrorDialog.Caption);
//Memo1.Clear;
Memo1.Text := 'Erro de Sistema: '+#13#10+ E.Message;
end;
end;
It really would help if you would show us the scripts you are trying to execute,
or at least the script which does not execute correctly. In any case, your code is wrong
because the documentation
states
It is good practice to call the ValidateAll method before the ExecuteAll method.
Note the 'before'. Your ValidateAll is after ExecuteAll, not before. Both are Boolean
functions but you are not checking their results, which you should.
With some trivial experimenting I found that I can provoke a EMSSQLNativeException
using SqlServer 2014 with the code below:
procedure TForm2.Button1Click(Sender: TObject);
const
sScript = 'select m.*, d.* from master m join detail d on, m.masterid = d.masterid';
var
FDScript : TFDSqlScript;
begin
try
FDConnection1.Connected := True;
FDScript := FDScript1.SQLScripts.Add;
FDScript.SQL.Text := sScript;
//FDScript1.ExecuteAll;
//FDScript1.ValidateAll;
// FDScript1.ValidateAll then
//FDScript1.ExecuteAll;
FDQuery1.SQL.Text := sScript;
FDQuery1.Open();
except
//EMSSQLNativeException
on E: EMSSQLNativeException do
begin
//ShowMessage('Erro'+FDGUIxErrorDialog1.ErrorDialog.Caption);
//Memo1.Clear;
Memo1.Text := 'Erro de Sistema: '+#13#10+ E.Message;
end;
end;
end;
Note the blatantly wrong syntax in the Sql statement, namely the comma after the on.
When FDQuery1.Open is called, this exception is raised (and caught initially by the debugger)
---------------------------
Debugger Exception Notification
---------------------------
Project sqlerror.exe raised exception class EMSSQLNativeException with message '[FireDAC][Phys][ODBC][Microsoft][SQL Server Native Client 11.0][SQL Server]Incorrect syntax near 'd'.'.
---------------------------
Break Continue Help
---------------------------
When I click Continue, execution proceeds into your exception handler, exactly as #TomBrunberg described in a comment
and the exception's message text is inserted into Memo1.
So I cannot reproduce the behaviour you describe based on the information in your question. It
must be caused by something you are not telling us, possibly in code you have
not included in your q or some property setting of the components you are
using.
Hopefully, trying the code above, you will find the debugger behaving as I
have described and this may give you some clue as to why you are getting the
problem you've described. Note that it is very important that you try the code
above in a new project, not your existing one. The only property setting
you need to do before executing it is to set FDConnection1 so that it can connect
to your server.
FWIW, if I uncomment-out the lines
FDScript1.ValidateAll then
FDScript1.ExecuteAll;
they execute without complaint, and I get the exact same behaviour as i've described without them.
I have a nasty error when executing a Firedac TFDScript error trying to drop a non-existent table:
Delphi Berlin 10.1 Upd 2
Database Firebird 2.5
It give an error when calling FDScript.ExecuteAll (it passes the FDScript.ValidateAll without any problem)
The code I am executing is as follows:
FDScript: TFDScript;
{...}
begin
FDScript.ScriptOptions.Reset;
FDScript.SQLScripts.Clear;
FDScript.SQLScriptFileName := '';
FDScript.ScriptOptions.CommandSeparator := ';';
FDScript.ScriptOptions.CommitEachNCommands := 1;
FDScript.ScriptOptions.DropNonexistObj := True; // seems to ignore this directive
FDConnection.StartTransaction;
try
FDScript.SQLScripts.Add.SQL.Add('drop table countries;');
FDScript.ValidateAll; // no errors here
ScriptStatus := GetEnumName(TypeInfo(TFDScriptStatus), Ord(FDScript.Status));
if FDScript.Status = ssFinishSuccess then begin
FDScript.ExecuteAll; // ERROR HERE! TABLE COUNTRIES DOES NOT EXIXTS
if FDScript.TotalErrors = 0 then begin
FDConnection.Commit;
end
else begin
FDConnection.Rollback;
end;
end
else begin
FDConnection.Rollback;
end;
except
FDConnection.Rollback;
raise;
end;
end;
Implementation seems to be correct. The engine checks if a raised exception is of ekObjNotExists kind, and if so and the DropNonexistObj property is enabled and command kind is DROP or ALTER, it logs to its console. Otherwise re-raises the caught exception.
The only explanation is then, that you are seeing an exception message dialog shown by debugger. These dialogs are displayed even for handled exceptions (so long you won't add them to an ignore list or turn this feature off, which you should not do).
List := FQueue.LockList;
for I := 0 to List.Count - 1 do
begin
Mail := TIdMessageTaskman(List[I]);
FEventLogger.LogMessage( 'Mail' + Mail.ToString, EVENTLOG_INFORMATION_TYPE , 0, 2);
try
try
FidSmtp.Connect();
FidSmtp.Send(Mail);
except
on e: exception do
begin
FEventLogger.LogMessage('Error sending mail ' + e.ClassName + ', ' +
e.Message, EVENTLOG_ERROR_TYPE, 0, 2);
MarkMailExecution(Mail.TaskID, Mail.NotificationID, False, e.Message);
Continue;
end;
end;
finally
begin
if FidSmtp.Connected then
FidSmtp.Disconnect;
end;
end;
FEventLogger.LogMessage( 'after finally', EVENTLOG_INFORMATION_TYPE , 0, 2);
MarkMailExecution(Mail.TaskID, Mail.NotificationID, True, '');
FreeAndNil(Mail)
So the following code works, but as soon as there is a problem sending an e-mail and the exception is raised, the service stops. Is there I way I can make it continue and go through all the Queue? Even if there are messages with errors. For example of an error that stops my service is when "I attach" a file that does not exist.
You said you've confirmed you get into the finally section. So there are 3 possibilities:
A line of code in the finally section blocks the code from continuing.
Another exception is raised in finally section.
When you enter the finally section, you're already in an "exception state". So leaving finally takes you to the next finally/except section in the call stack.
You'll have to add debug logging to confirm which, but I suspect number 3. Possible triggers for the existing exception state:
Your Mail instance is not valid, and your swallower caught an Access Violation. When you again try to use Mail in the except section, you get another Access Violation.
Something within MarkMailExecution triggers its own exception.
(I'm assuming your logging mechanism isn't failing because you have been getting some information from it.)
I have one IBDatabase in DataModule linked with my IBTransaction.
In one module of project I need to control the persistence in two database.
For this, I am adding the second IBDatabase this way:
constructor TConnections.Create(AIBDatabase: TIBDatabase);
begin
if AIBDatabase = nil then
raise Exception.Create('The base connection is needed!');
inherited Create;
FIBDatabase := TIBDatabase.Create(nil);
FIBDatabase.LoginPrompt := false;
FIBDatabase.Params.Clear;
FIBDatabase.Params.Text := AIBDatabase.Params.Text;
FIBDatabase.DatabaseName := AIBDatabase.DatabaseName.Replace('DB.GDB', 'DB2.GDB');
end;
procedure TConnections.SetTransaction(AIBTransaction: TIBTransaction);
begin
if AIBTransaction = nil then
raise Exception.Create('Then Transaction is needed!');
AIBTransaction.AddDatabase(FIBDatabase);
FIBDatabase.DefaultTransaction := AIBTransaction;
FIBDatabase.Open;
end;
Any select commands are work fine, but in insert command the error occurs.
Well, I have this:
connections := TConnections.Create(Dm.Database);
try
connection.SetTransaction(Dm.Transaction);
qry := TIBQuery.Create(nil);
qry.Database := Dm.Database;
try
// here are commands with Dm.Transaction
// ...
qry.ExecSql;
finally
qry.Free;
end;
otherQry := TIBQuery.Create(nil);
otherQry.Database := connection.OtherDatabase;
try
// here are commands with connection.OtherDatabase but same Transaction
// ...
otherQry.ExecSql; // The error occurs here.
finally
otherQry.Free;
end;
Dm.Transaction.Commit;
finally
connection.Free;
end;
'invalid transaction handle (expecting explicit transaction start)'
These block is envolved in try except.
So, if I try again, after the error, the process runs smoothly.
What's wrong in my configuration?
This may occur if you started transaction explicitly. Every explicit transactions must be finished explicitly. So, if your connection is open explicitly, you should close it explicitly.
You may use :
//Commit(Stop) the transaction before open an other connection
if Dm.Transaction.InTransaction then
dm.Transaction.Commit;
Note: In applications that connect an InterBaseExpress dataset to a client dataset, every query must be in its own transaction. You must use one transaction component for each query component.
http://docwiki.embarcadero.com/Libraries/XE8/en/IBX.IBDatabase.TIBTransaction
I have this method where i execute a sql statement and catch a error in a try except statement
AdoQuery := TAdoQuery.Create(self);
AdoQuery.connection := AdoConnection;
AdoQuery.SQL.Add(sqlStr);
AdoQuery.Prepared := true;
try
begin
AdoQuery.ExecSql;
AdoQuery.Active := false;
end;
except on e:eAdoError do
ShowMessage('Error while creating the table: ' + e.Message);
end;
I can catch the error like this and show it to the user but it's showing some useless info for the user. I Would like to show only the %msg part of the error, take a look at the pic:
I tought e.MEssage allow me to get only the %msg part but it give me the whole thing hardly understoodable by a random user. How do i get only the usefull info in this case
Table reftabtest.rPCE already exists
Thank you.
You can use the Errors property of the TADOConnection object, what you want is the Description member of the Error object.
In your case:
function ParseOBDCError(ErrorDescription : String) : String;
var
Ps : Integer;
Pattern : String;
begin
Pattern := '%msg:';
Ps := Pos(Pattern, ErrorDescription);
if Ps > 0 then
begin
Result := Copy(ErrorDescription, Ps+Length(Pattern)+1);
// if you want, you can clean out other parts like < and >
Result := StringReplace(Result, '<', , '', [rfReplaceAll]);
Result := StringReplace(Result, '>', , '', [rfReplaceAll]);
Result := Trim(Result);
end
else
Result := ErrorDescription;
end;
...
AdoQuery := TAdoQuery.Create(self);
AdoQuery.connection := AdoConnection;
AdoQuery.SQL.Add(sqlStr);
AdoQuery.Prepared := true;
try
AdoQuery.ExecSql;
AdoQuery.Active := false;
except on e : Exception do
begin
if AdoConnection.Errors.Count > 0 then
ShowMessageFmt('Error while creating the table: %s',
[ParseOBDCError(AdoConnection.Errors[0].Description)])
else
ShowMessageFmt('something went wrong here: %s', [e.Message]);
end;
end;
That message dialog wasn't shown by your ShowMessage() code.
First, the icon is wrong - that's not the ShowMessage icon.
Second, the text you added ('Error while creating the table: ') to the message is missing.
This means your exception swallower is not catching the exception, because it's not of the EADOError class. So what's happening is the application's default exception handler is showing the exception.
Before I explain how to fix it, I need to point out that your exception swallower is wrong (your's should not be misnamed an exception handler).
Because you're swallowing the exception: If another method calls yours it will incorrectly think you method succeeded, and possibly do something it shouldn't. You should never write code that makes an assumption that there isn't a significant call stack leading into your method.
Swallowing to show a message to the user doesn't help, because it hides the error from the rest of the program. Especially since, as you can see: there is already code in one place that tells the user about the error without hiding it from the rest of the program. (The problem you have is that you want a friendlier message.)
To fix it:
First find out what the actual exception class is, so you're able to catch the correct error.
Now you have a number of options, but the simplest is as follows:
First log the exception, preferably with call stack. You don't want to be stuck in the situation where the user gets a friendly message but you as developer lose critical information if you need to do some debugging.
To get the call stack you can consider third-party tools like Mad Except, Exceptional Magic, JCLDebug to name a few.
Now show the message to the user.
Finally call Abort. This raises an EAbort exception which by convention is a "silent exception". It tells the rest of the program that there was an error (so it doesn't do things assuming everything is fine). But by convention, any further exception handlers should not show another message to the user. This includes the default handler's dialog in your question.
If the default handler is incorrectly showing EAbort messages, then it should be fixed.