Replicating existing server data to clients

Hi, I have a xData Rest server with a Echo server implemented. My VCL clients have Echo client implemented. All works as expected.

My question is about how to get already existing data from server to client at startup of clients.

At startup of client, but before I subscribe to Aurelius events, I go through every entity I need on the client and "replicate" it.

procedure TdmReplication.CopyExistingData;
var
  lClient: TXDataClient;
  lLocalConn: IDBConnection;
  lLocalManager: TObjectManager;
begin
  // LEO 12.12.2022: Get LOCAL connection and manager
  lLocalConn := CreateConnection;
  lLocalConn.Connect;
  lLocalManager := TObjectManager.Create(lLocalConn);

  // LEO 12.12.2022:
  lClient := TXDataClient.Create;
  try
    lClient.Uri := 'http://tdwos/staweb/api';

    DoReplicate<TProductGroup>(   lLocalManager, lClient);
    DoReplicate<TFirmware>(       lLocalManager, lClient);

  finally
    lClient.Free;
    lLocalConn.Disconnect;
    lLocalManager.Free;
  end;
end;

And this:

function TdmReplication.DoReplicate<E>(aManager: TObjectManager; aClient: TXDataClient): boolean;
begin
  var lItemList := aClient.List<E>;
  for var lItem in lItemList do begin
    aManager.Replicate(lItem);
  end;
end;

From the above one can see that I replicate the entities TProductGroup and TFirmware.
The TProductGroup replicates just fine. The TFirmware does not unfortunately. The TFirmware has an assosiation to TProductGroup like this:

    [Association([TAssociationProp.Lazy, TAssociationProp.Required], CascadeTypeAll - [TCascadeType.Remove])]
    [JoinColumn('ProductGroupID', [TColumnProp.Required], 'ID')]
    [ForeignKey('FK_Firmware_ProductGroup_ProductGroupID')]
    FProductGroupID: Proxy<TProductGroup>;
    function GetProductGroupID: TProductGroup;
    procedure SetProductGroupID(const Value: TProductGroup);

When replicating TFirmware I get this error:
Project SmartStudioPro.exe raised exception class ESQLiteException with message 'NOT NULL constraint failed: Firmware.ProductGroupID'.

As the data is coming from the server, all data is valid. All records in TFirmware has a valid ProductGroupID (FK) value.

What I see debugging the code is that for a TFirmware item being replicated, the field TFirmware.ProductGroupID = nil, when it should not be nil.

Any hints or suggestion to what I'm doing wrong here?

Hi @Olsen_Leif_Eirik,

How are you checking that? From Delphi watches, or logging that value from code?
Are you checking TFirmware.ProductGroupID property or TFirmware.FProductGroupId?

I believe this might relate to the fact ProductGroupId is proxied. But If you could try to gather more info, it will help.

I think one thing to look for is exactly the location where ProductGroupId is becoming or being interpreted as nil.

  1. Can you see the JSON response gathered from the XData server after you call aClient.List<TFirmware>?
  2. Right after that call, what do you see in the FProductGroupID? Does it have a proxy controller set?
  3. What happens if you try to force the proxy load by calling TFirmware(lItemList).ProductGroupID? Is the proxy loaded?

How are you checking that? From Delphi watches, or logging that value from code?
Are you checking TFirmware.ProductGroupID property or TFirmware.FProductGroupId ?

Yes I was using Delphi Watches. And, as my watch variable was the TFirmware, the property inspected ended up being TFirmware.FProductGroupID, not TFirmware.ProductGroupID.

  1. Can you see the JSON response gathered from the XData server after you call aClient.List<TFirmware>?

Hmm, not sure how to do that. Sorry.

  1. Right after that call, what do you see in the FProductGroupID? Does it have a proxy controller set?

This is from Delphi Watch:
FProductGroupID = (nil, TDeserializerProxyController($F9F6180) as IProxyController, False)

  1. What happens if you try to force the proxy load by calling TFirmware(lItemList).ProductGroupID? Is the proxy loaded?

Yes, if I force the proxy load by calling TFirmware(lItem).ProductGroupID it is loaded.
The loading of the proxy is also triggered if I just add the TFirmware(lItem).ProductGroupID to my Watch List during debugging.

For my associations in my data model, it seems all of them have then TAssociationProp.Lazy attribute. Maybe this is what what I'm seeing the effect of? If that is the case, then I guess my question would be if there is a way to force proxies to be loaded from code? That is without having to change my model?

All the information you provided is expected. Since you watched FProductGroupID, the value is nil because the proxy is not loaded yet (that's also what the False means). The information to load it is available as the TDeserializerProxyController).

When you watch the ProductGroupID or read it via code, the getter of the property is executed and the proxy is loaded.

And then, yes, you can force the loading of the proxies via code, but there will always be a limit, don't forget about that. Probably is not your case, but some models might have levels and levels of depth that proxies should be loaded.

In your case easiest way is doing this:

type
  TInternalXDataClient = class(TXDataClient)
  end;

begin
  { }
  TInternalXDataClient(Client).ForceProxyLoading := True;
  // Use Client
end;