Dirty entities not saving when saved directly or recursively

Posts   
 
    
ddp74
User
Posts: 16
Joined: 04-Jan-2024
# Posted on: 21-Aug-2025 14:19:45   

Details

  • LLBLGen 5.11.4

  • ASP.Net 4.7.2

  • Adapter

Summary

I've got a really odd issue I can't work out. Regardless of how we save some entities, LLBLGen is not saving them to the database, even though the entity is flagged as dirty. It doesn't matter whether the save is done using recursive true from the parent, or when using adapter.SaveEntity on the dirty entity itself.

I've enabled DQE tracing and there's no UPDATEs output for the dirty entities.

Entity Relations:

  • Entity (CustomListEntity)

    • Entity.CustomListOptions (CustomListOptionEntity)

      • CustomListOptionEntity.CustomListOptionMappingValues (CustomListOptionMappingValueEntity)
    • Entity.CustomListMappings (CustomListMappingEntity)

      • CustomListMappingEntity.CustomListOptionMappingValues (CustomListOptionMappingValueEntity)

aka

CustomList has many CustomListOption

CustomListOption has many CustomListOptionMappingValue

CustomList has many CustomListMapping

CustomListMapping has many CustomListOptionMappingValue

When building I add the CustomListOptionMappingValueEntity entities to the CustomListMapping entities, leaving the collection under CustomlistOptionEntity empty. Creations and deletions work as intended for all entities. Updates work for CustomListEntity.CustomListOptions and CustomListMappingEntity.CustomListOptionMappingValues. Updates for CustomListEntity.CustomListMappings do not go through. IsDirty is true before and after the call to SaveEntity(), and SaveEntity() is returning true to suggest the save was successful. However, the update query is never ran. Note that the child entities CustomListMappingEntity.CustomListOptionMappingValues save updates perfectly fine.

I have tried both saving via a recursive save on the top-level entity, and by manually going through the structure and calling SaveEntity() on each individual entity. In both cases the behaviour is seen.

private void SaveEntity(DataAccessAdapter adapter, CustomListEntity entity)
{
    adapter.SaveEntity(entity, true);

    foreach (CustomListOptionEntity optionEntity in entity.CustomListOptions)
    {
        adapter.SaveEntity(optionEntity, true);
    }

    foreach (CustomListMappingEntity mappingEntity in entity.CustomListMappings)
    {
        adapter.SaveEntity(mappingEntity, true); // Dirty entities are not saved

        foreach (CustomListOptionMappingValueEntity valueEntity in mappingEntity.CustomListOptionMappingValues)
        {
            adapter.SaveEntity(valueEntity, true);
        }
    }
}

For example, the CustomListMappingEntity has a field called MappingKey. If I change the MappingKey from "MaxPortions" to "MaxPortions123", and then call SaveEntity() directly on the mapping entity, then the SaveEntity() call returns a value of "true", but the save never happens. The IsDirty flag on the entity is true both before and after the call, with no query ran against the db.

The data is fetched using a PrefetchPath in case that's of any relevance:

public static IPrefetchPath2 GetCustomListPrefetchPath()
{
    IPrefetchPath2 root = new PrefetchPath2(EntityType.CustomListEntity);
    root.Add(CustomListEntity.PrefetchPathCustomListOptions);
    IPrefetchPathElement2 mappingSets = root.Add(CustomListEntity.PrefetchPathCustomListMappings);

    mappingSets.SubPath.Add(CustomListMappingEntity.PrefetchPathCustomListOptionMappingValues);

    return root;
}
Attachments
Filename File size Added on Approval
image (2).png 24,430 21-Aug-2025 14:20.02 Approved
NoChanges.png 353,499 21-Aug-2025 14:20.51 Approved
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39910
Joined: 17-Aug-2003
# Posted on: 22-Aug-2025 09:14:30   

The trace (use the verbose option) should tell you where it exits. So not only DQE tracing but also ORMPersistence tracing. Try only to save one of the entities that doesn't save, and check what the trace output produces. Also, check if you don't have any Authorizers active which prohibit the save or that the entity type in the designer is marked to not have updates allowed (open entity in editor -> mappings tab -> Allowed Actions, should be create/retrieve/update/delete)

IsDirty on the entity is just one of the things, if no field is flagged as changed (so IsChanged for the field in the Fields collection is false, use the entity.Fields.GetIsChanged(index) method for that) then no update is issued. If the PK isn't set, no update is issued as well.

If you can't find it, you could compile the DQE + runtime framework sourcecode yourself and step through the code. The sourcecode is on your disk in the installation folder, and also available under My Account -> downloads -> version -> Extra section on the website. Load the sln, remove all dqe's you don't use and remove the strong name key reference from the assemblyinfo classes.

Once you've compiled them, reference them in all your projects where you currently reference them. This is a bit cumbersome as the strong name is changed so you can't just drop in the assemblies.

If you have resharper or use Jetbrains Rider, you can step into the code without compiling the runtimes btw, they allow you to step into the runtimes by decompiling the code on the fly.

You can follow the code from the sourcecode yourself too btw: Code starts at DataAccessAdapterBase.SaveEntity, which produces an update and insert queue with work items. That's the first step, to see the entity ends up in a queue. If the queues stay empty, check the method DataAccessAdapterCore.PerformPreSaveEntityActions. If you look at the method, you'll see it has various trace exits with description what it checks.

If the entity ends up in the update queue, it'll end up in the DQE to be used to produce a query. This is started in the DataAccessAdapter.PersistQueue method. This might look a bit complicated, but it essentially produces queries and batches them and if a batch is completed, it'll execute the batch, or if no batching is active, will execute the query right away.

The query is produced using QueryCreationManager.CreateSaveQueryForEntity. The QueryCreationManager is an object in the adapter, which is created in the method DataAccessAdapterCore.CreateQueryCreationManager. This is a virtual method. You can override this method and e.g. create an instance of a derived class of the QueryCreationManager to intercept calls to it if you aren't able to compile the sourcecode. You then have to override the method QueryCreationManager.CreateQueryForEntityToSave and QueryCreationManager.CreateUpdateDQ (there are 2 overloads) to intercept the call to the DQE

But really, enable verbose tracing on the ORM support classes as well (and save the entity you think should be saved only). So enable verbose tracing on: SqlServerDQE, ORMGeneral, ORMQueryExecution, ORMPersistenceExecution

This will produce a lot of data, but if you save 1 entity it should be doable to see where it goes wrong.

Good luck!

Frans Bouma | Lead developer LLBLGen Pro
ddp74
User
Posts: 16
Joined: 04-Jan-2024
# Posted on: 22-Aug-2025 15:13:41   

Thanks! The correct tracing made it obvious:

Entity/Entities of type 'CustomListMappingEntity' not saved because Allowed Action Combinations for this entity doesn't contain action 'Update'.

It wasn't set to Update in the designer, which is odd as we've never explicitly told it to not to.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39910
Joined: 17-Aug-2003
# Posted on: 23-Aug-2025 18:27:36   

Maybe it didn't have a PK defined at one point... in any case, it should be an easy fix: specify the Update action and things should be saved simple_smile

Frans Bouma | Lead developer LLBLGen Pro