Retrieving data using LinqMetaData with an existing Context overrides the MarkedForDeletion and IsDirty states in already cached entities

Posts   
 
    
ioan
User
Posts: 12
Joined: 20-Nov-2018
# Posted on: 20-Nov-2018 16:30:23   

My scenario: I am trying to save in a transaction multiple entities, either dirty or marked for deletion. I retrieve them using LINQ queries and a single context, so that everything is in one place. After changing the state of some of them (dirty or deleted), there are some behaviors that receive the control to further process the business logic (orthogonal concerns like authorization, calculated field computation, whatever). Some of these behaviors use LINQ queries with the same context, which retrieve entities of which some are already cached in the context. The effect is that, after entity retrieval, the IsDirty and MarkedForDeletion flags are reset (false) for such entities.

Question: Is there any setting to tell the infrastructure: "for changed/deleted entities please do not change the flag/do not reload them/ignore them"?

Thanks in advance, Ioan

P.S. I use LLBLGen 5.4.1

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 20-Nov-2018 19:09:56   

So you want to reload entities but protect the IsDirty and MarkedForDeletion flags, correct? Or do you want to protect the entities and not reload them?

ioan
User
Posts: 12
Joined: 20-Nov-2018
# Posted on: 21-Nov-2018 12:24:12   

Walaa wrote:

So you want to reload entities but protect the IsDirty and MarkedForDeletion flags, correct? Or do you want to protect the entities and not reload them?

Either way would be acceptable in this phase, but I would rather protect the entities and not reload them. Options to control this behavior would be great!

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 21-Nov-2018 22:09:12   

Why are you using the same context for entities to be updated, and those used for computations and logic?

Also, for the computation part, you may want to fetch DTOs instead of entities. This should be faster, lighter and avoids messing with entities in the context.

ioan
User
Posts: 12
Joined: 20-Nov-2018
# Posted on: 22-Nov-2018 13:24:33   

The computation is not only to produce values but to update other entities in the graph. Example: When I change a node in the tree hierarchy, I also need to set some properties in the child nodes, at any level, so I need to load the subtree. Moreover, the behaviors represent cross-cutting concerns, among them authorization, which also needs to adjust some values in the entities.

So, in any of these behaviors we might need to access the database to retrieve data, and, at the end, persist all changes in one transaction. The behaviors are aggregated at runtime over IoC, so it is hard to impose any rules on them.

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 23-Nov-2018 02:16:51   

What if there are newer versions in the database of the entities you want to protect in the context. I.e. The entities were updated since the last fetch.

I think it's either you commit the transient changes, refetch the full graph, do the processing and updates and re commit all in one transaction.

Or you refetch the graph into another context, and traverse both graphs to compare entities and fields, skip whatever has been changed in the old graph or marked for deletion.

ioan
User
Posts: 12
Joined: 20-Nov-2018
# Posted on: 23-Nov-2018 08:38:42   

In both your scenarios you also cannot be sure that in the time after fetching everything from the database and writing back the entities were changed.

So I understand that you currently don't have a solution for my use case. Are there any plans to support indicating the context to not update the changed entities/change state flags?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 23-Nov-2018 10:20:33   

Markedfordelete is a flag used by the framework to determine this entity has/will be deleted. It won't save entities with that flag, however fetching an entity again, means it's the same instance but in a new entity class instance: you then get the situation where you have one instance in memory with the flag set for deletion, and one which doesn't have the flag set. Which one to pick?

The context is really a way to keep entity instances in the same entity class instances, but it's not a cache. So you fetch entities, you work on them, you throw them away. the context then helps with having the entity instance (data) in the same objects so you can pass the object around and know it's the same object used through out a graph in memory. (e.g. 10 orders of the same customer all reference the same customer entity class Object.)

That said, when fetching an entity using a context which already contains an instance of that entity (so an instance of Customer "CHOPS" is already in the context C) will return that instance and will toss away a newly fetched entity. (it's the behavior of the Add() method of the context). You're sure your context overrides this behavior and instead removes the instance in the context and replaces the one just fetched?

Marking entities as 'dirty' or 'deleted' manually isn't what you should do. If you want to delete entities, use a DataScope (https://www.llblgen.com/Documentation/5.5/LLBLGen%20Pro%20RTF/Using%20the%20generated%20code/gencode_datascopes.htm), which automatically uses removal trackers. Entities which should be deleted are automatically added to these trackers and it will produce a single unit of work for you to persist.

It won't solve the question what to do with 2 instances of the same entity where 1 has a flag set to a different value than the other because it can't do that. I must say that if one process marks an entity for deletion (or better: schedules an entity for deletion) and another fetches that same entity to do work on it, isn't that a bug?

Frans Bouma | Lead developer LLBLGen Pro
ioan
User
Posts: 12
Joined: 20-Nov-2018
# Posted on: 26-Nov-2018 13:26:50   

I took the time to study a bit what you wrote last time. You're right, the context is not a cache (I misused the 'cache' word), and I use it to hold a graph of entities for the time of processing data, as you indicated.

I do have a specialized context class (LLBLGenContext), but it does not change the behavior of the Add method, as it is not overridable, so everything happening in Add is under complete control of LLBLGen (this class adds application specifics to the context, so it is merely an add-on).

I tried to debug a little bit to see where the problem arises, but the breakpoint set in the MarkedForDeletion setter is only hit when I set the flag manually (I did it in the class CommonEntityBase, which is base for all the entities). So, apparently, after setting it nobody changes it, but still, when checking again, the flag is reset.

To clarify a bit about the behaviors. These are classes which take the responsibility of applying cross-cutting behaviors to the entities. One of these behaviors is to ensure, for an entity hierarchy, that the whole hierarchy gets deleted. So, when the root is deleted implies that the whole hierarchy is deleted, so the whole graph is marked as deleted. Another behavior checks some permissions (does the current user have permissions to delete the entities?), which may load, depending on the application logic, again some other part of the hierarchy. The behaviors do not know of each other, do not know what the others do, when the others are applied, and in which order. So they must ensure that the entities with which they work are loaded. The logic they are comfortable with is: "I need the data, expecting that if the entities are already in context, use them, if not, load them, but make sure you have them all". Currently they use a query using the aforementioned context to load the subtree, expecting that the query loader is smart enough to add to the context only the missing entities, leaving the others in place and untouched. The behaviors are aware that the entities may be in one of the 4 states: unchanged, changed, deleted, and new, and they adapt their logic accordingly.

Please note that the example above is only for clarification purposes, such behaviors can be many and doing various things. The behavior orchestrator makes sure the behaviors are applied properly.

Currently, I use a UnitOfWork2 to add the changed entities and eventually commit them, using the flags provided by LLBLGen (IsNew, IsDirty, MarkedForDeletion), which apparently, get overwritten in the above scenario.

I checked the DataScope class and maybe I can find a way to use it (how can I provide my "improved" context?), but if the logic I need to use will generate the same problem, it is useless. Maybe I am wrong, but it seems to me that the DataScope is more like UI oriented, while I need something more server-like, lightweight.

Do you think I have a chance to get my problem solved: "fetch me the data (query), expecting that if the entities are already in context, use them, if not, add them"?

Attachments
Filename File size Added on Approval
LLBLGenCache.zip 4,884 26-Nov-2018 13:30.27 Approved
ioan
User
Posts: 12
Joined: 20-Nov-2018
# Posted on: 26-Nov-2018 15:23:43   

Maybe a solution: in the base entity class we could override OnCanLoadEntity:


        /// <summary>
        /// Method which will check if the caller is allowed to fill this entity object with entity data from the database or not.
        /// </summary>
        /// <returns>true if the caller is allowed to load the data into this instance, otherwise false.</returns>
        protected override bool OnCanLoadEntity()
        {
            return !this.MarkedForDeletion && !this.IsDirty && base.OnCanLoadEntity();
        }

I think this should help, what do you think?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 27-Nov-2018 16:40:45   

The OnCanLoadEntity() is part of the authorization, but you can use it for this too of course: it will simply deny the entity loaded from the DB as if it's not fetched. That should indeed solve your problem, as far as I can see.

Other than that, there's no built-in logic which preserves the state you have in memory and throws away newly fetched instances of the same entity.

Frans Bouma | Lead developer LLBLGen Pro
ioan
User
Posts: 12
Joined: 20-Nov-2018
# Posted on: 27-Nov-2018 16:43:59   

Thanks for your answer, I will do some extensive testing and, if everything looks OK, I will close the thread.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39590
Joined: 17-Aug-2003
# Posted on: 27-Nov-2018 17:11:58   

ok simple_smile If you have updates, just post a new post in this thread, it will reopen itself automatically simple_smile

Frans Bouma | Lead developer LLBLGen Pro