Many-to-many mapping and saving

Hello.


We have, for example, next db schema (Oracle):

table Module (id number, title varchar2)
table Role (id number, title varchar2)


table Role_Module (role_id number, module_id)

Mapped classes:


  [Entity]
  [Table('MODULE')]
  [Id('FID', TIdGenerator.None)]
  TModule = class
  private
    [Column('ID', [TColumnProp.Required])]
    FID: double;
    [Column('TITLE', [TColumnProp.Required], 400)]
    FTitle: string;
  public
    property ID: double read FID write FID;
    property Title: string read FTitle write FTitle;
   end;
  

  [Entity]
  [Table('ROLE_MODULE')]
  [Id('FRole', TIdGenerator.None)]
  [Id('FModule', TIdGenerator.None)]
  TRoleModule = class
  private
    [Association([TAssociationProp.Lazy, TAssociationProp.Required], [])]
    [JoinColumn('MODULE_ID', [TColumnProp.Required], 'ID')]
    FModule: Proxy<TModule>;


    [Association([TAssociationProp.Lazy, TAssociationProp.Required], [])]
    [JoinColumn('ROLE_ID', [TColumnProp.Required], 'ID')]
    FRole: Proxy<TRole>;
    function GetModule: TModule;
    procedure SetModule(const Value: TModule);
    function GetRole: TRole;
    procedure SetRole(const Value: TRole);
  public
    property Module: TModule read GetModule write SetModule;
    property Role: TRole read GetRole write SetRole;
  end;



  [Entity]
  [Table('ROLE')]
  [Id('FID', TIdGenerator.IdentityOrSequence)]
  [Sequence('role_seq')]
  TRole = class
  private
    [Column('ID', [TColumnProp.Required])]
    FID: double;


    [Column('TITLE', [TColumnProp.Required], 400)]
    FTitle: string;


    [ManyValuedAssociation([TAssociationProp.Lazy, TAssociationProp.Required], CascadeTypeAll, 'FRole')]
    FModules: Proxy<TList<TRoleModule>>;


    function GetModules: TList<TRoleModule>;
  public
    constructor Create;
    destructor Destroy; override;
    property ID: double read FID write FID;
    property Title: string read FTitle write FTitle;
    property Modules: TList<TRoleModule> read GetModules;
  end;


Classes was generated by Data Modeler, I only change CascadeType set for ManyValuedAssociation to CascadeTypeAll.

Ok, now I want to create TRole object and insert (save) it.

  Role := TRole.Create;
  Role.Title := 'abc';
  RM := TRoleModule.Create;
  RM.Role := Role;
  RM.Module := ObjManager.Find<TModule>(1); //I already had item with ID = 1
  Role.Modules.Add(RM);


  ObjManager.Save(Role);

In result - Role object inserted in table ROLE, but table ROLE_MODULE is empty.
Ok, I try to save TRoleModule manually:

  ObjManager.Save(Role);

  for i := 0 to Role.Modules.Count - 1 do
    ObjManager.Save(Role.Modules[ i]);

but I got error: Exception class EObjectAlreadyPersistent with message 'Cannot turn persistent object of class TRoleModule. The object is already in persistent context.'

So, I can't understand, where I made a mistake? In mapping? In save methods?

Thanks for help.

You must save TRoleModule before saving TRole, otherwise Aureluis will think you want Update (not Save) the TRoleModule objects and include them in manager. Just change to this:


But before I save main object, it have no ID from sequence. Is where no problems?

Then do not specify TCascadeType.SaveUpdate in your ManyValuedAssociation. It doesn't make sense anyway, since your id generator is none, thus Aurelius can't tell if it must save or update (that's the source of the problems you have). Thus, if you need to save items manually, remove the automatic cascade. You can then save the role first, then the detail items later.

Ok, I tried, and received this error then call Manager.Save(Role.Modules):

I don't know why this error happens. Can you please maybe post the entire updated source code so we are sure everything is ok? Note that since you have associations Role and Module in TRoleModule, you don't need to add it to Modules property, you can just set Role and Module properties and save it directly.

Another try: I change ManyValuedAssociation as You wrote:



    [ManyValuedAssociation([TAssociationProp.Lazy, TAssociationProp.Required], CascadeTypeAll - [TCascadeType.SaveUpdate, TCascadeType.Merge], 'FRole')]
    FModules: Proxy<TList<TRoleModule>>;


and call this code:

  Manager.Save(Role);
  for i := 0 to Role.Modules.Count - 1do
    Manager.Save(Role.Modules);


On statement Manager.Save(Role) I got error 

Aurelius won't let you save objects which references transient instances indeed.

This is a very specific situation. You have used composed, manually-assigned id (which is already not recommended) and is mixing with classes that have auto-generated id. If this is a legacy database, ok, if it's not, I highly, strongly recommend you don't use this approach. Use an autogenerated id for the many-to-many table.
If it's not possible, unfortunately you would have to remove the ManyValuedAssociation (Modules property) to be able to save TRole object. You can then retrieve the associated TRoleModule objects using a query (TCriteria).

OK, I understand that I can't map this structure to Aurelius, but I think it's a mistake - add auto-generated key to table where it don't need.

I solved it by adding not needed field Id - there no another solutions :(