XDataWebDataset, Insert et SubpropsDepth is causing errors

Hello,

I have a dataset (TXDataWebDataset) loading an object with multiple sub objects that I get flawlessly thanks to the sub-props depth setting (myWebDataset.SubpropsDepth := 2).
I get an exception when trying to insert a new entry in the dataset, telling me subprop-fields are unknown.

here is a modified part of my code :

Model

  Entity]
  [Model('Model1')]
  [Table('COMMANDE')]
  [Sequence('GN_COMMANDE_ID')]
  [Id('FChrono', TIdGenerator.IdentityOrSequence)]
  TCommande = class
  private
    [Column('CHRONO', [TColumnProp.Required])]
    FChrono: Integer;  

    [ManyValuedAssociation([], CascadeTypeAll)] // , 'FCommande')]
    [ForeignJoinColumn('ID_ORDER', [TColumnProp.Required])]
    FLINES: TList<TLine>;
  public
    constructor Create;
    destructor Destroy; override;
    property Chrono: Integer read FChrono write FChrono;
    property LINES: TList<TLine> read FLINES write FLINES;
  end;

  [Entity]
  [Model('Model1')]
  [Table('LINE')]
  [Sequence('GN_LINE_ID')]
  [Id('FChrono', TIdGenerator.IdentityOrSequence)]
  TLine = class
  private
    [Column('CHRONO', [TColumnProp.Required])]
    FChrono: Integer;

    [Column('ID_ORDER', [TColumnProp.Required])]
    FCommande: Integer;
    //[Association([TAssociationProp.Lazy], CascadeTypeAllRemoveOrphan)]
    //[JoinColumn('ID_ORDER', [TColumnProp.Required])]
    //FCOMMANDE: Proxy<TCommande>;

    [Association([], CascadeTypeAllButRemove)]
    [JoinColumn('ID_PRODUIT', [])]
    FPRODUIT: TProduit;

  public
    property Chrono: Integer read FChrono write FChrono;
    property Commande: Integer read FCommande write FCommande;
    property PRODUIT: TProduit read FPRODUIT write FPRODUIT;
  end;

  [Entity]
  [Model('Model1')]
  [Table('PRODUIT')]
  [Id('FChrono', TIdGenerator.None)]
  TProduit = class
  private
    [Column('CHRONO', [TColumnProp.Required])]
    FChrono: Integer;

    [Association([TAssociationProp.Lazy])]
    [JoinColumn('ID_MASTER', [TColumnProp.NoUpdate])]
    FPRODUITMASTER: Proxy<TProduit>;

    [ManyValuedAssociation([TAssociationProp.Lazy], CascadeTypeAllButRemove, 'FProduit')]
    [OrderBy('CHRONO')]
    FPHOTOS: Proxy<TList<TPhotoProduit>>;

    [ManyValuedAssociation([TAssociationProp.Lazy], CascadeTypeAllButRemove, 'FProduitMaster')]
    FPRODUITSCHILDREN: Proxy<TList<TProduit>>;

    function getPhotos: TList<TPhotoProduit>;
    procedure setPhotos(Value : TList<TPhotoProduit>);
    function getProduitMaitre: TProduit;
    procedure setProduitMaitre(Value : TProduit);
    function getProduitsEnfants: TList<TProduit>;
    procedure setProduitsEnfants(Value : TList<TProduit>);
  public
    constructor Create;
    destructor Destroy; override;
    property Chrono: Integer read FChrono write FChrono;
    property PRODUITMASTER: TProduit read getProduitMaitre write setProduitMaitre;
    property PHOTOS: TList<TPhotoProduit> read getPhotos write setPhotos;
    property PRODUITSCHILDREN: TList<TProduit> read getProduitsEnfants write setProduitsEnfants;
  end;

  [Entity]
  [Model('Model1')]
  [Table('PHOTO_PRODUIT')]
  [Id('FChrono', TIdGenerator.None)]
  TPhotoProduit = class
  private
    [Column('CHRONO', [TColumnProp.Required])]
    FChrono: Integer;

    [Association([TAssociationProp.Lazy, TAssociationProp.Required])]
    [JoinColumn('ID_ART', [TColumnProp.Required])]
    FProduit: Proxy<TProduit>;

    function getProduit : TProduit;
    procedure setProduit(Value : TProduit);
  public
    property Chrono: Integer read FChrono write FChrono;
    property Produit: TProduit read getProduit write setProduit;
  end;

Insertion

   WDSetLine.SubpropsDepth := 0;
   try
     WDSetLine.Insert;
     //TJSObject(WDSetLine.CurrentData)['COMMANDE@xdata.ref'] := Format('Commande(%s)', [TJSObject(WDSetMaster.CurrentData)['chrono']]);
     //TJSObject(WDSetLine.CurrentData)['COMMANDE'] := JS.new(['chrono', WDSetMaster.FieldByName('Chrono').AsInteger]);
     WDSetLine.FieldByName('Commande').AsInteger := WDSetMaster.FieldByName('Chrono').AsInteger;
     TJSObject(WDSetLine.CurrentData)['PRODUIT@xdata.ref'] := Format('Produit(%d)', [myProductId]);
     WDSetLine.Post;
     WDSetLine.ApplyUpdates;
     Result := True;
   finally
     WDSetLignes.SubpropsDepth := 2;
   end;

I removed the link-back to the master table (lines in //commentary) to be able to move on, but the link to the product must stay available on the table, as well as the photographs associated to the product. So I tried reducing the SubpropsDepth to 0 with... 0 difference.

on ApplyUpdates when the association to the master table is activated, I get

Property "COMMANDE.LINES" does not refer to a known property in type "XData.Default.Line"

the child refers to the master, that refers to the children... so i commented to only keep the link master-child.

But with current code, I get the exception :

Property "PRODUIT.PRODUITMASTER" does not refer to a known property in type "XData.Default.Line"

So I don't really know how to solve any of the problems here.

Are you able to build a project reproducing the issue? We can then fix the project for you with the correct code. It looks like the dataset is creating the dotted properties like PRODUIT.PRODUITMASTER in the JavaScript object itself. One workaround is simply remove such properties from the underlying JS object before applying updates.

Sorry for the delay, we had a hard time rebuilding servers that burnt with OVH data center...

can't you tell me what could prevent JS for creating the whole structure, but just the object I need ?
I don't understand why setting SubpropsDepth to 0 have no effects.

as you spotted it, the underlying Javascript object EditRow in the dataset contains the whole structure of parent and children element. when i do WDSetLine.Insert, I get :

EditRow: {…}
COMMANDE: null
"COMMANDE.Chrono": null
"COMMANDE.LINECOMMANDE": null
"COMMANDE.Nom": null
Chrono: null
PRODUIT: null
"PRODUIT.Chrono": null
"PRODUIT.PHOTOS": null
"PRODUIT.PRODUITMASTER": null
"PRODUIT.PRODUITMASTER.Chrono": null
"PRODUIT.PRODUITMASTER.Libelle": null
"PRODUIT.PRODUITMASTER.PHOTOS": null
"PRODUIT.PRODUITMASTER.PRODUITMASTER": null
"PRODUIT.PRODUITMASTER.PRODUITSCHILDREN": null
"PRODUIT.PRODUITSCHILDREN": null
"PRODUIT.Prix": null

Please try the following: open file XData.Web.JsonDataset.pas in <xdata>\source\core\web folder. Then in this method (around line 180):

procedure TXDataObjectFieldMapper.SetJSONDataForField(const FieldName: string;
  FieldIndex: Integer; Row, Data: JSValue);
begin
  inherited;

end;

Remove the inherited keyword, leaving the method implementation blank. Please let me know if this solves the problem.

Thanks for your answer.
Alas, removing theinherited doesn't have any effect in resulting project.js .
I have to do it after every compilation directly within the project js :

rtl.createClass(this,"TXDataObjectFieldMapper",pas.JSONDataset.TJSONObjectFieldMapper,function () {
    [...]
    this.SetJSONDataForField = function (FieldName, FieldIndex, Row, Data) {
      //pas.JSONDataset.TJSONObjectFieldMapper.SetJSONDataForField.apply(this,arguments);
    };

But after this manipulation, created objects are entirely null objects, it removes properties from first to last level. It raises exceptions in my whole application...

There are two XData.Web.JsonDataset files. Are you sure you are modifying the correct one?

I modified the one within \core\web folder, since the one in core parent folder doesn't have this procedure.

Are you able to provide a small project reproducing the issue?

neither the sparkle server nor the client are online, and I won't have the right to provide you with the server... creating a sample out of it would take too much time.

I will send you in private the client so you can give me (I hope) new hints.

By the way, I search for the JSDelete procedure. Didn't find it in JS unit...

We have received your project. Thank you, but it's rather big and complex for a support ticket. Five units with several web datasets in it, HTML templates, etc. First thing I'd suggest is that you create a sample project/small server from scratch to both a) provide us with a sample project we can effectively analyze, debug and focus on the issue; b) learn from it.

The JSDelete procedure is declared in JS unit.

js.pas pointed in my units is from (Delphi 10.3)
user\Documents\tmssoftware\TMS WEB Core RSXE12\Component Library Source.
I found the unit you're talking about in
user\Documents\tmssoftware\TMS WEB Core RSXE12\Core Source\RTL

should I replace the link to the folder in delphi win32 library paths ?

What do you mean by "pointed in my units"?
The first folder should be in your Delphi library path. The second folder should be in your Web Core library path.

OK, on completion the procedure JSDelete cannot be found since the Web Core libraries are not in Delphi win32 Library path.
But compilation does work.
I used JSDelete to remove all the extra fields and everything's OK now.
Maybe I should use something else to insert a new line (another dataset or direct request, or a service...) ?

Sorry, but I didn't understand what you meant?

I mean, I shouldn't use the dataset with all the child items but something else, and then re-load my dataset.

You have both options, the dataset is just a helper for you to bind objects to the visual controls. You can always manipulate the objects in other ways of course, directly from code, etc.