Using Unit of Work with RemovedEntitiesTracker is deleting all records in table

Posts   
 
    
timbered
User
Posts: 85
Joined: 09-Feb-2020
# Posted on: 24-Jan-2025 23:12:14   

LlblGen version 5.11, Firebird 5

My model is a ParticipantEntity 1:n relation to ChildEntity

I have the following code:

        Dim NewP = New ParticipantEntity
        NewP.LastName = "Smith"
        NewP.Child.Add(New ChildEntity With {.Name = "One"})
        NewP.Child.Add(New ChildEntity With {.Name = "Two"})
        NewP.Child.Add(New ChildEntity With {.Name = "Three"})
        NewP.Child.Add(New ChildEntity With {.Name = "Four"})
        NewP.Save(True)
        Dim NewID = NewP.Id
        Debug.Print("New record = " & NewID & ":" & NewP.Child.Count.ToString)

        Dim RereadFirst = New ParticipantEntity(NewID)
        RereadFirst.Child.RemovedEntitiesTracker = New ChildCollection
        Dim ChildTwo = RereadFirst.Child.Where(Function(x) x.Name = "Two").FirstOrDefault
        RereadFirst.Child.Remove(ChildTwo)
        'Here, Child.count = 3

        Dim uow As New UnitOfWork
        uow.AddForSave(RereadFirst, True)
        If RereadFirst.Child.RemovedEntitiesTracker.Count > 0 Then uow.AddDeleteMultiCall(RereadFirst.Child.RemovedEntitiesTracker, Nothing)
        uow.Commit(New Codeimation.CCMIS.ORM.Firebird.HelperClasses.Transaction(IsolationLevel.ReadCommitted, "UOW"), True)
        'Here, RereadFirst.Child.Count is still 3

        Dim RereadSecond = New ParticipantEntity(NewID)
        'Here, RereadSecond.Child.Count = 0, and the entire Child table is empty

After that Commit, ALL the record in the Child table have been deleted.

What am I doing wrong?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39801
Joined: 17-Aug-2003
# Posted on: 25-Jan-2025 09:26:59   

The bug is here: uow.AddDeleteMultiCall(RereadFirst.Child.RemovedEntitiesTracker, Nothing) This will add a call for directly delete entities from the database, and as you specified no filter/predicate, all rows were deleted from the table the entity type of the collection is mapped on. (See https://www.llblgen.com/Documentation/5.11/ReferenceManuals/LLBLGenProRTF/html/DC6FAB1E.htm )

To delete the entities in the collection with a unit of work, you should call uow.AddCollectionForSave(RereadFirst.Child.RemovedEntitiesTracker)

Frans Bouma | Lead developer LLBLGen Pro
timbered
User
Posts: 85
Joined: 09-Feb-2020
# Posted on: 25-Jan-2025 10:00:53   

Oops. Figured it was something simple.

Since I'm "saving" the removed entities, the framework will recognize that they really need deleting?

timbered
User
Posts: 85
Joined: 09-Feb-2020
# Posted on: 25-Jan-2025 10:08:00   

Also, my Child entity has a 1:n relationship with a ChildDetail entity. By deleting the Child, all the ChildDetail records should be deleted as well (with Cascade set) correct?

timbered
User
Posts: 85
Joined: 09-Feb-2020
# Posted on: 25-Jan-2025 10:21:10   

Hummm....

Here: https://www.llblgen.com/Documentation/5.11/LLBLGen%20Pro%20RTF/Using%20the%20generated%20code/SelfServicing/gencode_usingcollectionclasses.htm

says to use uow.AddCollectionForDelete

(which I obviously should have read first, but anyway...)

So, Save or Delete?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39801
Joined: 17-Aug-2003
# Posted on: 26-Jan-2025 08:09:27   

flushed oops I wasn't the sharpest when I wrote my reply friday morning it seems. To delete entities in a collection you indeed need to call AddCollectionForDelete...

However that's not going to recurse to 1:n related entities (as these aren't in the collection). The 'easiest' is to add a cascade rule to the FK pointing from ChildDetail to Child and specify Delete, so any row deleted from Child will trigger a delete of all related rows in ChildDetail. If other rows in other tables point to ChildDetail then you might want to do the same to the FKs in those tables. This is 'set and forget' but it does require an additional rule to the FK constraint which you have to add to the production database as well.

Another way to do this is to call AddDeleteMultiCall to the uow for the deletion of all ChildDetail rows which point to one of the Child entities in the tracker collection. To do that you need to specify an IN predicate (so you get a DELETE FROM ChildDetail WHERE Pkfield IN (.... ) query) which is constructed using a FieldCompareRangePredicate and you specify the pk values of all Child entities in the tracker collection for the range. You could use the QuerySpec .In(arrayOfValues) extension method to produce that easily. To get the range of values you could use a linq query e.g. RereadFirst.Child.RemovedEntitiesTracker.Select(Function(f) f.PkField).ToList() (I hope I wrote the right VB.NET syntax)

What's important here is that you specify DeletesPerformedDirectly in the commit order before Delete. By default DeletesPerformedDirectly are scheduled after normal deletes which would be too late as you first have to delete the childdetail entities. To do so you can specify the right commit order in the constructor of the unit of work, or alter the collection you get back from the CommitOrder property. The order by default is:

UnitOfWorkBlockType.Inserts
UnitOfWorkBlockType.InsertsPerformedDirectly
UnitOfWorkBlockType.Updates
UnitOfWorkBlockType.UpdatesPerformedDirectly
UnitOfWorkBlockType.Deletes
UnitOfWorkBlockType.DeletesPerformedDirectly
Frans Bouma | Lead developer LLBLGen Pro
timbered
User
Posts: 85
Joined: 09-Feb-2020
# Posted on: 26-Jan-2025 09:01:40   

Great explanation, thanks!

I had the cascade rule set already, and it does indeed work correctly.

So, all is well. Thanks again!