UnitOfWork2 behavior change (breaking change)

Posts   
 
    
kievBug
User
Posts: 105
Joined: 09-Jan-2009
# Posted on: 30-Jan-2017 23:16:32   

Hey guys,

We have an issue when we use Unit Of Work. Basically we group all the changes(inserts, updates, and deletes) in one unit of work. We are trying to delete two related records, for example like this:

Document entity, has fields: Id int not null primary key, Name string, PrimaryShortCutID int nullable foreign key to ShortCut (ID)

ShortCut entity has fields: Id int not null primary key, Name string, DocumentID int not null foreign key to Document (ID)

So lets assume we have one record for each entity which reference each other by both PrimaryShortCutID and DocumentID. We want to delete the record from Document entity.

In 3.1 we did this: 1. We set PrimaryShortCutID to null and add Document to UoW as AddForSave 2. We delete record from related ShortCut, by adding it to UoW as AddForDelete 3. We delete record from Document, by adding it to UoW as AddForDelete

In 4.1 you did the change in UnitOfWork2.cs in ConstructSaveProcessQueues method to skip saving of entities which are in _entitiesToDelete collection, and this causes a problem with step #1 i.e. PrimaryShortCutID is not set to null and thus the delete from ShortCut entity fail.

As for now, we did a temporary fix to use two UnitOfWork instances one for saving another for deletion. Is there any other way to fix it for us, or you will have to fix it on your end?

Currently we are using the latest version 5.1. Adapter template, SQL Server database, LlblGen Pro Framework.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 31-Jan-2017 13:44:06   

You can specify the commit order in the UoW constructor. In your case, you should specify first Deletes, then the rest. See: http://www.llblgen.com/Documentation/5.1/LLBLGen%20Pro%20RTF/Using%20the%20generated%20code/Adapter/gencode_unitofwork_adapter.htm#specifying-the-order-in-which-the-actions-are-executed

Frans Bouma | Lead developer LLBLGen Pro
kievBug
User
Posts: 105
Joined: 09-Jan-2009
# Posted on: 31-Jan-2017 15:22:13   

We did specify the order, and it can't be delete first, because at first we need to set a field to null, and only after that we can delete. The problem is that UoW contains Save and Delete for the same entity - Save/Update sets field null.

And order of the execution doesn't affect it at all, because execution goes into UnitOfWork2.Apply method, after that it goes to PerformPreCommitActions, which calls ConstructSaveProcessQueues. In this method we create inQueue dictionary like this:


Dictionary<Guid, IEntity2> inQueue = _entitiesToDelete.Select(e => e.Entity).ToDictionary(e => e.ObjectID);
foreach( UnitOfWorkElement2 element in elementsToProcess )
{
if( !inQueue.ContainsKey( element.Entity.ObjectID ) && !alreadyProcessed.ContainsKey(element.Entity.ObjectID))
    {
...

So all the entities from _entitiesToDelete, won't be added to the _entitiesToUpdate list. So basically actionquery for update won't be created.

If you need, I can create an example for you, but I think the code is pretty clear.

Otis wrote:

You can specify the commit order in the UoW constructor. In your case, you should specify first Deletes, then the rest. See: http://www.llblgen.com/Documentation/5.1/LLBLGen%20Pro%20RTF/Using%20the%20generated%20code/Adapter/gencode_unitofwork_adapter.htm#specifying-the-order-in-which-the-actions-are-executed

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 31-Jan-2017 23:48:50   

I think UoW DeleteEntitiesDirectly can be handy here, try to use it instead of the Entities Queue.

Otherwise I could have advised a cascade delete rule in the database and execute one delete command.

P.S. we will check out the breaking code.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 01-Feb-2017 10:23:38   

Ah I see now. Yes we made that change, because an entity marked for delete is of no use saving (it's removed right afterwards), so we skipped them in the save process. In general you won't run into this problem, unless you have a cycle in your model, like you have. A cycle in general requires multiple statements anyway so we considered this change justified (also because no-one normally would run into this: in a normal graph you wouldn't run into this: it's never necessary to delete first, simply because in a normal graph you only need to save the FK side first and then delete the PK side which is possible with the default setup.).

With a cycle in the model, you have a problem indeed, because there are two dependent sides. Modeling cycles isn't a good idea in general (persisting a graph with these cycles is also problematic). You could opt for a 3rd entity which defines the dependencies so you don't have the cycle, but it might be you have to work with the setup at hand and can't change it (so for next time you have to create a cyclic dependency, please use a 3rd entity wink )

I agree with Walaa, using the DeleteEntitiesDirectly delegate option of the UoW in this case can help you with doing it in 1 go, still. Or you could opt for UpdateEntitiesDirectly for updating the FK in Document to null. You can specify these actions in the order to execute so you can decide when which is executed as well. That would solve your problem.

Frans Bouma | Lead developer LLBLGen Pro
kievBug
User
Posts: 105
Joined: 09-Jan-2009
# Posted on: 01-Feb-2017 18:57:34   

Yeah we have a lot of cycles, and it is a real life, those are everywhere. It will add complexity to the model to use 3rd entity, or do other workarounds.

Usage of direct methods Update/Delete, may have a side effects especially with deletes, i.e. entity won't be the same as after it is deleted by a regular delete. So as for now we split those into two separate UoW, which are executed one after another. This solves the problem for us.

I understand your explanation, but again my expectation is that such service (deleting, saving cycles correctly) is provided by ORM, and I do not have to deal with it as a user in my code.

Thanks, Anton

Otis wrote:

Ah I see now. Yes we made that change, because an entity marked for delete is of no use saving (it's removed right afterwards), so we skipped them in the save process. In general you won't run into this problem, unless you have a cycle in your model, like you have. A cycle in general requires multiple statements anyway so we considered this change justified (also because no-one normally would run into this: in a normal graph you wouldn't run into this: it's never necessary to delete first, simply because in a normal graph you only need to save the FK side first and then delete the PK side which is possible with the default setup.).

With a cycle in the model, you have a problem indeed, because there are two dependent sides. Modeling cycles isn't a good idea in general (persisting a graph with these cycles is also problematic). You could opt for a 3rd entity which defines the dependencies so you don't have the cycle, but it might be you have to work with the setup at hand and can't change it (so for next time you have to create a cyclic dependency, please use a 3rd entity wink )

I agree with Walaa, using the DeleteEntitiesDirectly delegate option of the UoW in this case can help you with doing it in 1 go, still. Or you could opt for UpdateEntitiesDirectly for updating the FK in Document to null. You can specify these actions in the order to execute so you can decide when which is executed as well. That would solve your problem.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39613
Joined: 17-Aug-2003
# Posted on: 02-Feb-2017 09:29:23   

This problem is caused by cycles, and our framework doesn't directly support cycles: if you save a graph with cycles, it won't work either. This isn't the same as 2 entities having a relationship, it's 2 entities having both FK's to the other. The designer issues warnings for when it detects cycles, to notify you it might be a problem at runtime.

I agree, if we'd state we do support cycles, then this should work out of the box, however as this is caused by cycles (and like I said, with saving you have the same problem) it's not supported as a feature, sorry.

Frans Bouma | Lead developer LLBLGen Pro