views:

87

answers:

1

When I re-throw a EUpdateError exception in the TDatasetProvider.OnUpdateError event, it is not recognized as EUpdateError exception in the catch block. It's only recognized as base Excption.

try
  ...
  //calls the TDatasetPorvider.OnUpdateError event.
  myClientDataSet.ApplyUpdates(0);
  ...
except
 on ex: EUpdateError do
 begin
   //never goes here
   //Evaluate ex.ErrorCode
 end;
 on ex: Exception do
 begin
   //always goes here
   //the expression (ex is EUpdateError) returns false;
 end;
end;

Hiere is the corresponding .OnUpdateError implementaion:

procedure MyDataModule.MyDatasetProviderOnUpdateError(..;E: EUpdateError;...);
beign
  //Here, the expression (E is EUpdateException) returns true;
  raise E;
end;

The exception is re-thrown, but as it seems the EUpdateError is transformed into a plain base Execption.
Does anybody know, why the class type get lost?
I would need that type in order to check the .ErrorCode to know what went wrong and to prepare the proper user message.

+1  A: 

Unfortunately, the "old-style" DataSnap server exceptions are marshalled to the client as text only (E.Message) so the exception class name and instance data are lost in the process. See SConnect unit, TDataBlockInterpreter.InterpretData method (the except block).

EDIT: Here's a very simplistic example to give you an idea (not tested at all):

// new methods

function TDataBlockInterpreter.ReadException(const Data: IDataBlock): Exception;
var
  Flags: TVarFlags;
  AClassName, AMessage, AContext: string;
  ErrorCode, PreviousError: Integer;
  OriginalException: Exception;
begin
  AClassName := ReadVariant(Flags, Data);
  AMessage := ReadVariant(Flags, Data);
  if AClassName = 'EUpdateError' then
  begin
    AContext := ReadVariant(Flags, Data);
    ErrorCode := ReadVariant(Flags, Data);
    PreviousError := ReadVariant(Flags, Data);
    OriginalException := ReadException(Data);
    Result := EUpdateError.Create(AMessage, AContext, ErrorCode, PreviousError, OriginalException);
  end
  // else if AClassName = ... then ...
  else
    Result := Exception.Create(AMessage);
end;

procedure TDataBlockInterpreter.WriteException(E: Exception; const Data: IDataBlock);
begin
  WriteVariant(E.ClassName, Data);
  WriteVariant(E.Message, Data);
  if E is EUpdateError then
  begin
    WriteVariant(EUpdateError(E).Context, Data);
    WriteVariant(EUpdateError(E).ErrorCode, Data);
    WriteVariant(EUpdateError(E).PreviousError, Data);
    WriteException(EUpdateError(E).OriginalException, Data);
  end;
end;

// modified methods

procedure TDataBlockInterpreter.DoException(const Data: IDataBlock);
begin
  raise ReadException(Data);
end;

procedure TDataBlockInterpreter.InterpretData(const Data: IDataBlock);
var
  Action: Integer;
begin
  Action := Data.Signature;
  if (Action and asMask) = asError then DoException(Data);
  try
    case (Action and asMask) of
      asInvoke: DoInvoke(Data);
      asGetID: DoGetIDsOfNames(Data);
      asCreateObject: DoCreateObject(Data);
      asFreeObject: DoFreeObject(Data);
      asGetServers: DoGetServerList(Data);
      asGetAppServers: DoGetAppServerList(Data);
    else
      if not DoCustomAction(Action and asMask, Data) then
        raise EInterpreterError.CreateResFmt(@SInvalidAction, [Action and asMask]);
    end;
  except
    on E: Exception do
    begin
      Data.Clear;
      Data.Signature := ResultSig or asError;
      WriteException(E, Data);
      FSendDataBlock.Send(Data, False);
    end;
  end;
end;
TOndrej
Is there a way to send the (sql/database) error code to the try-except block, in order to distinguish the error message (in order to make decisions on the error code)?
max
Yes, but you'd have to modify and rebuild both server and client executables. You could include the class name and have some utility functions to stream the particular exception instance data, like SQL error code.
TOndrej
Actually, I don't have a separated server client application. The TDataSetProvider and the TClientDataSet components are in the same DataModule (it's a single executable).
max
Even for a single executable, the principle is still the same: the server code (provider) "sends" the exception (currently, only its Message, along with the asError flag) so you have to modify that code to include the exception class name and instance data. The client code has to be able to recognize the exception class in order to read and interpret the instance data.
TOndrej
Is there an easy way to do that? I don't have an idea where or how to do. Or, do you have short sample code?
max
I've added a simple example.
TOndrej