The object manager dictionary reports a "duplicates not allowes" exception when creating a new object manager on an existing connection

Hello!

Our architecture is like so:
We instantiate a connection if none is assigned and then create a new object manager.
When doing so, we occasionally get "Duplicates not allowed" and I have no idea why (we're using the trial on that machine because this is another company, not my own stuff, so I can't easily debug this).

As remoting framework we're using mORMot (which has its own issues anyway).

Suggestions around what may be going on?

Thanks!

Hi Andrea,

Unfortunately, that is too few information. I don't know what could be causing this. We need call stack, at the very least a few lines of any Delphi code to have a barely idea of what's going on.

Hi, okay, let's see if I can make it easier.
This is my Aurelius-mapped class (note: this comes straight from the TMS Data Modeler):

[Entity]
  [Table('VRTAMBITI')]
  [UniqueKey('DESCRIZIONE')]
  [Id('FID', TIdGenerator.IdentityOrSequence)]
  TVRTAMBITI = class
  private
    [Column('ID', [TColumnProp.Required, TColumnProp.NoInsert, TColumnProp.NoUpdate])]
    FID: Integer;

    [Column('DESCRIZIONE', [TColumnProp.Required], 50)]
    [MinLength(10,'Servono almeno 10 caratteri')]
    FDESCRIZIONE: string;

    [Column('USER_INS', [], 50)]
    FUSER_INS: Nullable<string>;

    [Column('DATA_INS', [])]
    FDATA_INS: Nullable<TDateTime>;

    [Column('USER_UPD', [], 50)]
    FUSER_UPD: Nullable<string>;

    [Column('DATA_UPD', [])]
    FDATA_UPD: Nullable<TDateTime>;
  public
    property ID: Integer read FID write FID;
    property DESCRIZIONE: string read FDESCRIZIONE write FDESCRIZIONE;
    property USER_INS: Nullable<string> read FUSER_INS write FUSER_INS;
    property DATA_INS: Nullable<TDateTime> read FDATA_INS write FDATA_INS;
    property USER_UPD: Nullable<string> read FUSER_UPD write FUSER_UPD;
    property DATA_UPD: Nullable<TDateTime> read FDATA_UPD write FDATA_UPD;
  end;

The connection is currently created like so:

function TSettings.GetConnection: IDBConnection;
begin
  Result := TMSSQLConnection.Create(FSQLServerSettings.ToString);;
end;

I changed it to this just to understand if there was some kind of conflict, but it makes no difference.
Then we have a set of generic interfaces IEntityLoader, IEntitySaver, etc. that accept any class with corresponding objects and then we have the TAureliusXXX that map everything onto Aurelius:

TAureliusLoader<T:class> = class( TEntityLoader<T> )
  strict private
    FObjectManager: TObjectManager;
  strict protected
    procedure Load(ID: integer);override;
    property ObjManager: TObjectManager read FObjectManager;
  public
    constructor Create( AObjManger: TObjectManager );overload;
    destructor Destroy; override;
  end;

  TAureliusSaver<T:class> = class( TEntitySaver<T> )
  strict private
    FObjectManager: TObjectManager;
  strict protected
    procedure Save(Entity: T); override;
    procedure Save(Entities: TArray<T>);override;
    property ObjManager: TObjectManager read FObjectManager;
  public
    constructor Create( AObjManger: TObjectManager );overload;
    destructor Destroy; override;
  end;

  TAureliusDeleter<T:class> = class( TBFiveBOInterfacedObject, IEntityDelete<T> )
  strict private
    FObjectManager: TObjectManager;
  strict protected
    procedure Delete( Entity: T );overload;
    procedure Delete( Entities: TArray<T> );overload;
    property ObjManager: TObjectManager read FObjectManager;
  public
    constructor Create( AObjManger: TObjectManager );overload;
    destructor Destroy; override;                                         00
  end;

The code for these is that which you probably expect: the create accepts an object manager and then Load, Save and Delete do the right thing respectively, in this way:

procedure TAureliusLoader<T>.Load(ID: integer);
begin
  inherited;
  Entity := ObjManager.Find<T>(Id);
end;

procedure TAureliusSaver<T>.Save(Entities: TArray<T>);
begin
  FObjectManager.UseTransactions := True;
  inherited Save(Entities);
end;

procedure TAureliusSaver<T>.Save(Entity: T);
begin
  inherited;
  ObjManager.SaveOrUpdate(Entity);
end;

procedure TAureliusDeleter<T>.Delete(Entities: TArray<T>);
var
  Entity: T;
begin
  FObjectManager.UseTransactions := True;
  for Entity in Entities do
  begin
    Delete(Entity);
  end;
end;

procedure TAureliusDeleter<T>.Delete(Entity: T);
begin
  if Assigned( Entity ) then
    ObjManager.Remove(Entity);
end;

Save works just like you expect when you're adding a new item but the get trips everything up when you have an existing item,
Delete also does not work as intended but throws a weird message out:
Project BFiveServiceVCL.exe raised exception class EObjectAlreadyDetached with message 'Cannot remove or detach object of class TVRTAMBITI. The object is not in persistent context.'.
The code for the delete in the service is as follows:

procedure TAmbitiService.Ambito_DELETE(ID: integer;
  ResultStatus: TResultStatus);
var
  ResultObj: IEntityLoad<TVRTAMBITI>;
begin
  with IIntegerValidation( TPositiveIntValidation.Create ) do
  begin
    Candidate := ID;
    if  not Validate then
    begin
      ResultStatus.Error(RESULT_CODE_REQUIRED_PARAMETERS_MISSING, SessionUser.UserLocale, 'id');
      Exit;
    end;
  end;

  try
    ResultStatus.Error(RESULT_CODE_ITEM_NOT_FOUND, SessionUser.UserLocale);
    ResultObj := IEntityLoad<TVRTAMBITI>(TAmbitoLoad.Create(NewObjectManager));
    ResultObj.Load(ID);
    IEntityDelete<TVRTAMBITI>( TAmbitoDelete.Create( NewObjectManager ) ).Delete(ResultObj.Entity);
    ResultStatus.Success;
  except
    on E: Exception do
      begin
        HandleException(E, ResultStatus);
      end;
  end;
end;

As you can see we first load the object and then delete it. To my mind, the fact we're loading the object puts it in the persistent context. Is that not so?

Yet the get is what baffles me the most because it sometimes works and sometimes doesn't.
This is the service code:

procedure TAmbitiService.Ambito_GET(ID: integer;
  ResultStatus: TResultStatus; Result: TAmbito);
var
  ResultObj: IEntityLoad<TVRTAMBITI>;
begin
  try
    ResultObj := IEntityLoad<TVRTAMBITI>(TAmbitoLoad.Create(NewObjectManager));
    ResultObj.Load(ID);
    Result := TAmbito.Create(nil);
    Result.id := ResultObj.Entity.id;
    Result.Descrizione := ResultObj.Entity.Descrizione;
    if not ResultObj.Entity.USER_INS.IsNull then
      Result.DataLog.UserIns := ResultObj.Entity.USER_INS.Value;
    if not ResultObj.Entity.DATA_INS.IsNull then
      Result.DataLog.DataIns := DateTimeToUnix(ResultObj.Entity.DATA_INS.Value);
    if not ResultObj.Entity.USER_UPD.IsNull then
      Result.DataLog.UserUpd := ResultObj.Entity.USER_UPD.Value;
    if not ResultObj.Entity.DATA_UPD.IsNull then
    Result.DataLog.DataUpd := DateTimeToUnix( ResultObj.Entity.DATA_UPD.Value );
    if Not Assigned( Result ) then
      ResultStatus.Error(RESULT_CODE_ITEM_NOT_FOUND, SessionUser.UserLocale, Int32ToUtf8(ID));
  except
    on E: Exception do
      begin
        ResultStatus.Error(RESULT_CODE_EXCEPTION_TRAPPED, SessionUser.UserLocale, UnicodeStringToUtf8(E.Message));
        raise;
      end;
  end;
  if not Assigned( Result ) then
    ResultStatus.Error(RESULT_CODE_ITEM_NOT_FOUND, SessionUser.UserLocale);
end;

When it does not work, it'll sometimes throw out the message that the ID is not mapped (which is a lie) and sometimes the duplicates not allowed in the internal Aurelius dictionaries as soon as I try to get a new object manager. I wonder if I should not be freeing the object manager, which I currently do in the destroy if it's assigned.
When no error is generated server side, however, when I close the server my ReportOnMemoryLeaks := True shows this:


I can't make heads or tails of what exactly the problem is because it shows in so many different ways. There may be more than one problem and even then I am at a loss as to what they may be.

Thanks!

I'm afraid you will have to review all your code. I'm not sure if it's safe and correct that you cast objects to interfaces like you are doing.

Second, you are mixing object manager instances. Your code might be doing that all the time in complex operations, but in this one it's very clear:

You are creating a manager, retrieving ResultObj from it, and they creating another one to delete the same ResultObj. Obviously, the second manager doesn't know about ResultObj, since you loaded it in another manager, and that's why you get the "object is not in persistent context". You should use the same manager to delete the object.

In your second code, I can't see the error at first sight, but I need the exact message (exception) and the call stack at the moment of the error so at least we can have an idea of the exactly line causing the error and the code flow that happened until that line.