RE: VersionedConcurrencyControl error when adding a child entity (after deleting one)

Hi Mark, in case you don't want (or can't) wait for the update, here is the updated XData.Client.pas file with the improvements. Just replace your file with this one and call tms build to ask Smart Setup to recompile packages and binaries.

Hi Wagner,

Thanks for the XData.Client.pas file. I have used it and so far it all seems to work correctly (I have tried multiple scenarios to make it fail, but haven't succeeded :slight_smile: ).

Having said that, initially it did raise the Concurrency exception, but that was my mistake. Of course, in addition to (note the additional server-side updated properties):

procedure TfrmXData.ObjectUpdate(Dataset: TDataSet; AObject: TObject);
var
  LUpdated: TObject;
begin
  LUpdated := FXDataClient.PutFetch(AObject);
  TData_Base(AObject).Modified := TData_Base(LUpdated).Modified;
  TData_Base(AObject).RowVersion := TData_Base(LUpdated).RowVersion;
end;

I also had to implement:

procedure TfrmXData.ObjectInsert(Dataset: TDataSet; AObject: TObject);
var
  LUpdated: TObject;
begin
  LUpdated := FXDataClient.PostFetch(AObject);
  TData_Base(AObject).Created := TData_Base(LUpdated).Created;
  TData_Base(AObject).Modified := TData_Base(LUpdated).Modified;
  TData_Base(AObject).RowVersion := TData_Base(LUpdated).RowVersion;

  FXDataClient.ReturnedEntities.Add(AObject); // Memory management
end;

Once I did that, all is working OK. Thanks!

PS: I presume this will be included in the next release, so that an update won't break my code?

Hi Mark, yes, the changes will be included in next release.

1 Like

Hi Wagner,

I have started to build a new XData application and I am still/again running into issues with concurrency control, using a TXDataClient. In this application I have data forms that consume an ID and then retrieve a master entity into a TAureliusDataSet (adsMaster). All other TAureliusDataSets on the form are linked to associated data of the master entity (i.e. through master entity dataset fields).

Before going into specific details - and perhaps construct a demo app - I’d like to confirm my understanding is correct>

In this initial design, I simply have Save and Cancel actions that are linked to the State of the master dataset (i.e. enabled when State in dsEditModes). My thought was that simply calling adsMaster.Post would be sufficient for posting all pending changes (master and children). This seems to be the case indeed, but presents a problem when adding/editing an additional child entity (i.e. after the first).

This triggers a version exception on the first child entity that was not edited (confirmed by Version number in exception message). Apparently) - and that’s what I’d like to confirm - posting the master also updates ALL child entities (including the ones NOT changed). Because they were not changed, ObjectUpdate (see earlier in this ticket) is not called for the child dataset so that the Version field is “out of sync” and raises the exception.

Is my thinking correct and if so, how can I fix this?

Hi Mark,

Honestly, I cannot say with 100% confidence that your thinking is correct, because it depends on lots of variables and configurations. It depends on how is your cascade configured. It depends on how associations you circular associations you have. I really have to analyze the specific case to see what's going on.

"To fix this", either you send me the code so I understand what is going on, or you simply make your client code less complex and more verbose, having more control over the changes you are making. The dataset approach and the "multiple associations" approach is RAD and helpful, but if it's getting too complex, sometimes it's better just to make it simpler.

Hi Wagner,

Thanks for the feedback. I was already afraid it was too complex a situation to provide a “magic wand solution”. I’ll do some further experimenting and will revert with either the solution I found or a small demo app.

One thing though: will a TXDataClient post/put all associated (1-M) regardless? Or does that strictly depend on the association definition of the entities? If it should only SaveUpdate what changed, then obviously something in my code causes the issue etc….

Hi Wagner,

Hopefully you can help me out here. This issue is eating my lunch by now :frowning:

The problem I’m facing can be reproduced in the attached demo app. Unzip to a folder and build VersionDemo_Server and VersionDemo. Both will created in Win32 subfolder which has a sample SQLite database - with some test data - included.

Run the server and app and you’ll see a grid with some sample invoice data. Select invoice no 2 and click the Edit button. Now you have a form with the invoice, a 1-1 record (Info) and 1-M (Items).
You can edit all you want in the grid and everything is OK. However, editing in the grid will put the Invoice (parent) dataset also in Edit Mode. The problem starts when you save the entire invoice. Check the yellow edit and yellow grid column; the row version of the TInvoice entities go out of sync (i.e. fall behind on the updated Invoice row version, which will be incremented). If you now edit and attempt to post an Invoice Item in the grid, you’ll get the Concurrency exception.

I have attempted many things like:

  • Omitting the parent reference field from the child data set. It alleviates the problem, but only for editing. When inserting/appending, this doesn’t work because you cannot set the parent reference (i.e. InvoiceItem.Invoice).
  • Various Cascade options, but no luck…

I’m assuming the solution lies in either setting the correct cascade options or in somehow refetching the parent reference entities (in Items) to ensure the Version field is updated. However, I’m out of options and probably no longer “seeing the forest through the trees” so hopefully you can give me some clue(s). Ideally, I want one Save/Cancel button combination for the entire form.

VersionDemo.zip (177.0 KB)

PS: Apologies for the Invalid Pointer exception when closing (destroying) the Invoice form but I couldn’t figure this out quickly and want to make progress on the real application.

Dear Mark, I believe the solution lies in also updating the version number of the row items, in your ObjectUpdate method, or simply - and better in my opinion - that you do a "full reset". If you are using complex object tree, and you are updating all of it, and you want version control in lots of objects, the safest and saner is simply refresh everything to make sure you have the latest data.

For example, you could simply do:

procedure TfrmInvoice.saveInvoiceExecute(Sender: TObject);
begin
  adsInvoice.Post;
  SetInvoiceID(FInvoiceID);
end;

To make sure the full invoice is reloaded properly.

Hi Wagner,

Thanks for the solution! It works well and also provides an option to call some Service functions in between the two statements (e.g. to perform some complex calculations, better done on the server). However…..

Whilst implementing this in my real application, I ran into another version control problem. When posting an appended (or inserted) child record, I got the VersionedConcurrencyControl exception again!! Notably, on the first child record in the list. Not the inserted record (?).

After numerous hours of debugging, I finally figured out where the error occurs and how to fix it. I have “translated” this back to the Invoice - InvoiceItems example:

What didn’t work was:

ManyValuedAssociation([TAssociationProp.Lazy], CascadeTypeAllRemoveOrphan, 'FInvoice')]
FInvoiceItems: Proxy<TList>;

Changing this to the following solved the issue:

ManyValuedAssociation([TAssociationProp.Lazy], [TCascadeType.SaveUpdate, TCascadeType.Merge, TCascadeType.Remove], 'FInvoice')]
FInvoiceItems: Proxy<TList>;

Now here’s the thing: the change doesn’t make a difference in the VersionDemo app, provided earlier, it only happens in my own application. The difference being that the demo app uses SQLite and my application uses SQL Server 2016. I’m quite happy to leave it at that but it would be good to fully understand some things:

  1. The difference in cascade options (effectively) is RemoveOrphan missing from the working definition. Yet if I remove an item from the children, it’s nicely removed from the database (I tested and confirmed this)? As I read it (/TCascadeType.html)Remove means the child is deleted from the database if the parent is deleted from the database. If correct, why does it then work with RemoveOrphan missing?
  2. The working cascade options is actually the one generated by the Data Modeler, yet in the online documentation ( Mapping | TMS Aurelius documentation ) it’s recommended to use CascadeTypeAllRemoveOrphan option. In my mind, the latter makes more sense but the test I did for 1. above seems to contradict this. What’s your take?
  3. Why would the database used make a difference in this scenario?

I appreciate your time and consideration. For me this is important, as I am setting up the framework for a large application so I need to get the foundation right.

Hi Mark,

Even though they start with "Remove", the cascade types Remove and RemoveOrphan are not much related.

Remove means that if parent is deleted, children are deleted. The Aurelius operation would be something like that:

Manager.Remove(Parent);

So Parent is deleted from database, and if its children have TCascadeType.Remove in their cascade options, the children will also be deleted from the database.

RemoveOrphan relates to what happens to an item that is removed from its parent collection. The Aurelius operation is like this:

Parent.Children.Remove(FirstChildren);
// Could also be this, not that the line above and below are just Delphi list manipulation
// no Aurelius or database involved yet
Parent.Children.Delete[0];

// Now perform
Manager.Flush(Parent);

The above operation will cause Aurelius to check the children. It will realize there is one children missing in the parent, and will "remove" that missing from the list.

But default, removing the child from the list is an operation like this:

ALTER TABLE CHILDREN SET PARENT_ID = NULL WHERE ID = :child_id

The child is removed from the list, meaning it's not associated with the parent anymore. The child becomes "orphan". It still exists, but doesn't belong to any parent.

However, if TCascadeType.RemoveOrphan is included in the cascade options, then Aurelius will behave differently, and instead of making the child orphan, it will simply delete it from the database. That's what RemoveOrphan does.

I don't know how this relates to the issues you have. As I said before, when using complex object graphs like this, I would prefer to simply work with dedicated DTOs, work with simple Delphi object that are not Aurelius entities, that makes it much less complex.

Regarding the database difference, the only thing I can think of is that SQLite by default does not enforce foreign keys. You have to explicitly enable that enforcement. So maybe, just maybe, this is causing some different behavior in both databases, although it doesn't look so given the nature of the error you are getting.

Hi Wagner,

Thanks for the explanation but:

  • My understanding of Remove and RemoveOrphan was already correct. Thanks for confirming.

  • I have the simplest setup already. An (Aurelius) DataSet with a parent entity and a dataset with child entities, sourced from a dataset field in the parent. There are other associations in the parent but they’re all Lazy and never used (in this form).

  • The working cascade option ([TCascadeType.SaveUpdate, TCascadeType.Merge, TCascadeType.Remove]) is missing RemoveOrphan, yet if I delete a child entity via the grid (dataset) on the form it is correctly removed from the database (I have physically confirmed this). Something I wouldn’t expect given my understanding and your explanation.

  • Using the CascadeTypeAllRemoveOrphan option the error occurs when posting the parent with a new child - not yet posted (dsInsert). The attached screenshot shows what happens and the resulting call stack. What I completely fail to understand is why a Row Version conflict occurs on an entity that was never edited? It puzzles me even more why removal of the RemoveOrphan option alleviates this issue whilst removing a child still functions correctly in that case.

Screenshots.pdf (59.7 KB)

I’ll see in the coming days if I can find the time to develop a quick demo using SQLServer. I presume you can “rewire” the connection on a SQL Server on your side?

Thanks for your time and attention so far!

By simplest setup I mean not using Aurelius entities and transferring them from client to server and relying on all the complex Aurelius persisting mechanism.

I mean just using plain Delphi objects, send them from client to server via a service operation, and then manually save/update the object tree (object by object) server-side. This gives you full control over the persistence process.

Me neither. As before, I have to have the project and reproduce this myself here, debug thoroughly to understand what is going on. That's the kind of complexity that I'm suggesting you to avoid.

I presume you can “rewire” the connection on a SQL Server on your side?

If you are asking if I have an SQL Server here, yes, I do.

I prefer to keep my app as is. It's working as required now, but this cascade thing keeps nagging me.

I will need some time, but will build a SQL Server demo to demonstrate the issue. Stay tuned!

1 Like

Hi Wagner,

I tried to rewire my demo app to SQL server and it worked without issue. I.e. database ruled out as culprit. I then scrapped my entire data form and rebuilt it from scratch but again, the same error occurred :upside_down_face:

As a final attempt, I then decided to rebuild the demo app with my data model. It should be rather self-explanatory, but for logging daily operations the model contains a TDay with a 1-M association to TActivity. Even with a SQLite database this yields the same VersionedConcurrencyControl exception I have been trying to debug for the best part of a week now.

As the model has entities “above” TDay (TOperation → TWellbore → TWell) and foreign keys are enforced, I have pre-populated the include database. It’s therefore imperative you use the included database, as I have not coded anything to input the higher levels!

To see the problem, please do the following:

  1. Unzip the project group to a folder.
  2. Open the project group and build all (debug). The server and application will be generated to \Win32 with the dcu’s to \Win32\dcu.
  3. Run VersionDemo_Server without debugging.
  4. Run VersionDemo with debugging. This should open with 1 TDay entity selected in the grid.
  5. Click [Edit] to open the Day edit form.
  6. This should open the edit form with one TActivity row showing.
  7. Click [Append row]. This will append a row and pre-populate the necessary fields but leave the record in dsInsert mode.
  8. Now click [Save entire invoice] (sorry, forgot to adjust the label, but it will post the entire TDay as you can see in the code) and you’l see the VersionedConcurrencyControl exception. Note the RowVersionof the “violating” entity; it’s clearly the first child entity and that was not even edited! Why would this be modified?

You may close and re-open the form to see that if you edit/post individual rows, there is no problem. Only if you insert and post the entire Parent does the problem appear.

As commented in the Entities.WellDoc unit (line 180), changing from CascadeTypeAllRemoveOrphan to [TCascadeType.SaveUpdate, TCascadeType.Merge, TCascadeType.Remove] appears to solve the issue but it in my mind it’s semantically incorrect. Removed TActivity rows are correctly deleted from the database, but without RemoveOrphan this should not be the case, should it?

The actual error actually occurs WITHIN the adsDay.Post statement but BEFORE the LoadData call (unit Form.VersionDemo_Day, line 98). I have tried to debug into this but with the embedded datasets etc., I simply keep getting lost.

By elimination, I have come to the conclusion that the clue lies somewhere in the data model, but I cannot figure out where and I certainly don’t know how to solve it.

With your in-depth knowledge of Aurelius and XData, can you pinpoint what the problem is and how to solve it? Hopefully you can so that I can finally move on and start building my application! :grinning_face:

VersionDemo2.zip (167.0 KB)

Thank you for your the detailed and well-crafted demo, it helps a lot and speeds up.
The problem seems to be here:

procedure TfrmDay.FormCreate(Sender: TObject);
var
  ii: Integer;
begin
  for ii := 0 to ComponentCount - 1 do
  begin
    if Components[ii] is TAureliusDataSet then
    begin
      TAureliusDataSet(Components[ii]).FieldByName('ID').Required := false;
      TAureliusDataSet(Components[ii]).FieldByName('Modified').Required := false;
      TAureliusDataSet(Components[ii]).FieldByName('RowVersion').Required := false;

      if TAureliusDataSet(Components[ii]).FindField('LockLevel') <> nil then
        TAureliusDataSet(Components[ii]).FieldByName('LockLevel').Required := false;

      TAureliusDataSet(Components[ii]).OnNewRecord := LinkDay;

      TAureliusDataSet(Components[ii]).OnObjectInsert := ObjectInsert;
      TAureliusDataSet(Components[ii]).OnObjectUpdate := ObjectUpdate;
      TAureliusDataSet(Components[ii]).OnObjectRemove := ObjectRemove;

      TAureliusDataSet(Components[ii]).RefreshObjectOnCancel := true;
    end;
  end;
end;

You are setting such events in all datasets. That makes OnObjectInsert to be called for the newly inserted activity, causing it to be persistent, have a version number, etc., etc., and the next call (update the day) of course has outdated data.

Again, sorry for being repetitive, you have been bitten for all the automatic stuff happening under the hood. It's always complex. But that is the cause for this specific issue.

Hi Wagner,

Please forgive me for going on and on about this subject, but it’s just not resolved:

  1. First and foremost; in the original demo I sent (see earlier in this ticket), the same construction (ObjectXXX implemented) works flawlessly (with the Invoice / InvoiceItems data model).
  2. I have hooked the ObjectXXX procedures only to the child dataset. It then works OK for the children (editing and inserting), but the parent (TDay) is then not persisted to the database (i.e. Version not incremented).
  3. This entire setup with ObjectXXX procedures came forth from this post. In order to persist entities to the database, I somehow need to implement them. VersionedConcurrencyControl error when adding a child entity (after deleting one) - #5 by wlandgraf

Given the above observations, are you willing to further investigate (compare)? If not, what would be your recommended strategy to edit Parent/1-M Children in a form? My current design (load Parent nd use dataset fields seem the most straightforward and simplest approach. Surely this must be possible with XData?

Why do you say it works flawlessly if you have just asked for help in something that is not working?

The thing is, you insert a record in the child dataset. You have set an ObjectInsert event. This causes your client to ask the server to save the activity. The server then sends a JSON like this:

{
    "$id": 1,
    "@xdata.type": "XData.Default.Activity",
    "ID": "",
    "Modified": "1899-12-30",
    "RowVersion": 0,
    "DT_From": "2026-02-12T01:00",
    "DT_To": null,
    "Hours": 1,
    "PhaseCode": "DRL",
    "ActivityCode": 100,
    "ActivityClass": "P",
    "RootCause": null,
    "MD_End": null,
    "Comments": null,
    "Day": {
        "$id": 2,
        "@xdata.type": "XData.Default.Day",
        "ID": "11704767-F47D-49A1-875E-E7AED5CBDE08",
        "Modified": "2026-03-13T16:27:15.070",
        "RowVersion": 1,
        "LockLevel": "llNone",
        "ReportNo": 1,
        "ReportDate": "2026-02-12",
        "Operation@xdata.proxy": "Day('11704767-F47D-49A1-875E-E7AED5CBDE08')/Operation",
        "DayInfo": [],
        "Activities": [{
            "$id": 3,
            "@xdata.type": "XData.Default.Activity",
            "ID": "0273B30B-99F8-4F4A-B7EC-89119D4485AD",
            "Modified": "2026-03-10T21:06:54.820",
            "RowVersion": 16,
            "DT_From": "2026-02-12T00:00:01",
            "DT_To": null,
            "Hours": 1,
            "PhaseCode": "ABC",
            "ActivityCode": 900,
            "ActivityClass": "P",
            "RootCause": null,
            "MD_End": 90,
            "Comments": "RgBpAHIAcwB0ACAAYQBjAHQAaQB2AGkAdAB5AA==",
            "Day@xdata.proxy": "Activity('0273B30B-99F8-4F4A-B7EC-89119D4485AD')/Day",
            "Bha": null
        }]
    },
    "Bha": null
}

Because you are asking to save the activity, but the activity has associations, and XData automatically send the respective associations. This cause your object tree to be persisted/updated, Aurelius will traverse through the associated day, and then through the associated activity (the previous one). Somehow, such activity has its version number updated.

I can investigate better why the version number of the previous activity is being incremented if you want, the process is complex. Will let you know asap, but my point is this can go on and on and on.

Because it works flawlessly (i.e. no concurrency errors) with a demo data model (Invoice/InvoiceItems) that I sent you initially. Rebuilding the same, with my intended data model (TDay/TActivity) does not. The two demo apps included in this ticket (VersionDemo (Invoice) and VersionDemo2 (Day)) demonstrate this.

This is what has me stupefied. I fail to understand how this can be, even more because the error occurs on the first child that was NOT inserted or edited (why would that be updated by a parent post !?)….

Anyway, I don’t want to take more of you time so let’s leave it at that. I’ll continue for a bit more and if I can’t figure it out, I’ll abandon it all together.

Thanks for you help!

I'm investigating and it looks like the existing activity is being updated because of the Comments blob field that is being sent in the JSON.

If you see the JSON, the Comments object is being sent:

            "ActivityClass": "P",
            "RootCause": null,
            "MD_End": 90,
            "Comments": "RgBpAHIAcwB0ACAAYQBjAHQAaQB2AGkAdAB5AA==",
            "Day@xdata.proxy": "Activity('0273B30B-99F8-4F4A-B7EC-89119D4485AD')/Day",
            "Bha": null
        }]
    },
    "Bha": null
}

This is because the blob is loaded in the dataset to be displayed.
Thing is, as I said, this whole object tree is being sent when the new activity is inserted. Here is what Aurelius does:

  1. It detects first activity object is new (no ID), thus it creates a new instance and set the respective properties.
  2. Before INSERTing the activity in the database, is cascades the merge operation to the Day object
  3. For the day object, it detects day has an id and loads it from the database.
  4. Then it cascades for the actives of the day. For the only existing activity, it detects it has an id and loads it from the database.
  5. Note that Comments field is a lazy blob. That means that the previous operation (4) does NOT load the blob from the database, for performance reasons.
  6. Now that the cascade for the day object has finished, Aurelius performs an INSERT to save the new activity

Now, XData it will perform a Flush to make sure everything is up to date in the database.

  1. It checks if there are any modified columns in the new activity. None.
  2. It cascades to day and check for modified properties there. None.
  3. It cascades to existing activity and check if there are modified properties there. YES. Comments were "modified". Why? Because the existing comments are "unknown" (unloaded proxy) and by default and for performance reasons, Aurelius simply updates the blob content with the existing one.

That operation causes the version number for the existing activity to increase. And now your client application has an outdated activity, with an outdated version number. That causes a further update to fail.

Hi Wagner,

Even though I was not entirely sure I fully understood your explanation, I derived that the blob field (Comments) was likely causing the problem.

I changed it to an NVARCHAR(250) / String field and indeed - problem solved! :grinning_face: You can review this yourself in the attached VersionDemo3 demo app (please use embedded SQLite database for same reasons as before). I also applied it in my “real world” application and there it is solved as well now.

There’s only one thing I do not fully understand (yet): the association to a TBha entity does not seem to be a problem, regardless whether it has a value or it’s null. Does that make sense to you?

I suppose there’s no work-around for using a blob field, so I’ll settle for the NVARCHAR field.

Thanks for your patience and ultimately leading me to the solution!

VersionDemo3.zip (414.7 KB)

PS: Feel free to make all this ticket 100% public if you think other users may benefit from it in the future.