- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
Hidden relationship
Joined: 30-Jun-2005
Frans,
It looks like your template code sets it up so SetupSync<[MappedFieldNameRelation]> doesn't set up an AfterSave syncronization if there is not an "OppositeRelation" present. I am in a situation where I needed to hide this "OppositeRelation" and this is causing update problems (as the AfterSave isn't being handled, so the PK-FK syncronization doesn't happen).
Just to get updates to work, I have removed the <[If OppositeRelationPresent]> from the template in SetupSync<[MappedFieldNameRelation]>, DecoupleEvents<[MappedFieldNameRelation]>, and DesetupSync<[MappedFieldNameRelation]>. Is this an ok thing to do? I guess I don't understand why the FK of a child would not be updated by the parent saving in a graph where the child is pointing right at the parent....?
In a database system, relations aren't really defined in actual constructs, the only things there are FK constraints, which are defined on the FK side's table.
In LLBLGen Pro, object relations are actually a relation pair: A 1:n B results in B m:1 A. These aren't 'the same relation', but are 2 separate relations. In a situation of A 1:n B, it means that A knows of B because it has a 1:n relation with B.
Ok, when I hide the relation from the PK side to the FK side, so I hide A 1:n B, but keep B m:1 A, it means that A doesn't know of any B. This results in the fact that A doesn't have a collection of B's. B on the other hand has a reference to A.
If A doesn't know of B, it can't trigger synchronization with B, as it doesn't know of any B. This is the key in this problem. The synchronization is triggered on the PK side, and shouldn't result in an effect in B because A doesn't know of any B. It would be 'weird' if A doesn't know of any B, but still can sync with B, how would that be possible?
Another thing is how syncing is setup. Say you have the A 1:n B and the B m:1 A in place. I now can do: myA.Bs.Add(myB); and myB.A = myA;
both result in the same effect: myB will be part of myA.Bs and myB.A will be set to myA. This is done via re-use of code, so myB.A = myA simply calls into A and calls myA.Bs.Add(myB), which sets up the relation between myB.A and myA. As LLBLGen Pro doesn't use a 'central' session object which controls all this, this is done through code in an entity object (or its base class).
In your post you mention that you have to hide the PK -> FK relation. I wonder why that is necessary. Could you elaborate on that a bit, please?
Joined: 30-Jun-2005
Here's an abstract of our situation: A B 1:N B C 1:N C D M:1 (D here is just a code table)
When I manually delete an element of C, I remove it from B's collection and store it to be deleted on update of A (the delete is implemented in DataAccessAdapter). However, a D entity still has a reference to the to-be-deleted C in its collection. This D entity may still be around because there is another, not to be deleted C entity hanging around. If I had modified the to-be-deleted C element in any way that would call for an update, when I run a Save of A, after deleting this C entity, it would try to update this C entity which results in an error. This happens because of a circular graph situation. I tried to break this circular graph situation by hiding the D relationship back to C, but this causes problems when I try and add a new C (and attach a D to it)...as the D primary key never makes it into the new C's FK.
So, there it is!
EDIT: I just realized, can I just desync the to-be-deleted entity? All I really need to do is completely get the to-be-deleted entity out of the graph before the update happens...
Joined: 30-Jun-2005
Otis wrote:
Yes that's solved by simply do myC.D = null; or myD.Cs.Remove(myC); Or set the myC.FKtoD to another value.
I need to code it in an abstract way, though, so when I do myB.Cs.delete(myC), where delete(entity) is a function I wrote), anything in the graph that points to myC will lose its reference to myC (by collection or otherwise). I would rather not go through the whole serialized graph looking for references to myC....there is some kind of DesetupSync I can run on myC, isn't there? I'm at home right now and can't look at the code
There is, but that expects parameter values you don't have at that moment. If you want to prevent C from being saved, just set myC.Fields.IsDirty to false. It then isn't saved.
Also, the Cs collection in myB is part of myB, not of some other entity. What I therefore wonder is: why is that delete function part of that collection? Because if you want to wipe myC from your system, you can remove it from one entity's collection, but another entity can still reference it so you can't control that on the entity level.
Joined: 30-Jun-2005
Otis wrote:
There is, but that expects parameter values you don't have at that moment. If you want to prevent C from being saved, just set myC.Fields.IsDirty to false. It then isn't saved.
Also, the Cs collection in myB is part of myB, not of some other entity. What I therefore wonder is: why is that delete function part of that collection? Because if you want to wipe myC from your system, you can remove it from one entity's collection, but another entity can still reference it so you can't control that on the entity level.
Well, I guess thats just how everyone here was comfortable with it. When you want to delete myC, you do myA.myB.Cs.Delete(myC), then SaveEntity(myA). Never really thought that this was wrong, but I guess I see your point. The way we do it just assumes a simplifiied, if somewhat inccorect, view of the entity graph where each entity has only one true parent, and anything else that has a PK-FK relationship with the entity is not a true parent. These should never really be a situation where these "fake parents" should need a collection of "fake children." I tried to model this by eliminating the relationships from the "fake parents" to their "fake children" but as I said before, this ends up screwing up the PK-FK syncronization.
Here's an example. Employee EmployeeDependent 1:N EmployeeDependent Relationship N:1
The Relationship table here is what we call a "Code Table." It just has a code and description. In cubes, this is just a dimension table I think. An example of a relationship is "Son". IF an employee has 2 sons, he will have 2 employeedependent rows that both point to the "son" row of Relationship. Say we want to delete one of the son EmployeeDependent entities, and this entity was recently modified. Naturally, for me, I would want to do EmployeeA.EmployeeDependents.Delete(sonInstance). Then SaveEntity(EmployeeA).
Is just setting sonInstance.IsDirty = False the best way to do this? Can you think of another, better way to handle deletes (other than Context as our group has decided this would be too awkward).
Thanks
mikeg22 wrote:
Otis wrote:
There is, but that expects parameter values you don't have at that moment. If you want to prevent C from being saved, just set myC.Fields.IsDirty to false. It then isn't saved.
Also, the Cs collection in myB is part of myB, not of some other entity. What I therefore wonder is: why is that delete function part of that collection? Because if you want to wipe myC from your system, you can remove it from one entity's collection, but another entity can still reference it so you can't control that on the entity level.
Well, I guess thats just how everyone here was comfortable with it. When you want to delete myC, you do myA.myB.Cs.Delete(myC), then SaveEntity(myA). Never really thought that this was wrong, but I guess I see your point. The way we do it just assumes a simplifiied, if somewhat inccorect, view of the entity graph where each entity has only one true parent, and anything else that has a PK-FK relationship with the entity is not a true parent. These should never really be a situation where these "fake parents" should need a collection of "fake children." I tried to model this by eliminating the relationships from the "fake parents" to their "fake children" but as I said before, this ends up screwing up the PK-FK syncronization.
Ok, if you assume that there's just one reference, then that setup will work of course. but if there are more, it won't. The ambiguity of removing something from one collection, what it really means is the reason why it's sometimes a bit cumbersome to get right.
What's key is that myC is dereferenced from the graph if you want to save the graph without having myC being saved as well, unless you make myC appear not changed (dirty) so the graph sorter simply skips it.
Here's an example. Employee EmployeeDependent 1:N EmployeeDependent Relationship N:1
The Relationship table here is what we call a "Code Table." It just has a code and description. In cubes, this is just a dimension table I think. An example of a relationship is "Son". IF an employee has 2 sons, he will have 2 employeedependent rows that both point to the "son" row of Relationship. Say we want to delete one of the son EmployeeDependent entities, and this entity was recently modified. Naturally, for me, I would want to do EmployeeA.EmployeeDependents.Delete(sonInstance). Then SaveEntity(EmployeeA).
Is just setting sonInstance.IsDirty = False the best way to do this? Can you think of another, better way to handle deletes (other than Context as our group has decided this would be too awkward). Thanks
![]()
The graph you save with SaveEntity is sorted using a normal directed graph algorithm. The sorted graph is then processed to see which of the entities in the graph have to be saved (new, dirty, pending FK sync ones etc.). If an entity isn't dirty, and doesn't have pending FK syncs (so it's not added to a collection inside a new entity), it's not saved.
This means that if you set myC's IsDirty flag manually to false, it's never considered as an entity to save and won't end up in the queue.
To delete it from the DB, you have to execute a delete statement for myC. Best way to do this in one go is to use a unitofwork object. So you add myA to the UnitOfWork to save, and add myC to the unitofwork to delete. You then set myC.Fields.IsDirty to false and commit the unitofwork. This will then first produce a save queue for the graph for A, which won't include myC itself, because that's not a changed entity, then it will execute a delete for myC.
Joined: 30-Jun-2005
We are pretty much saving the UnitofWork in the graph itself I suppose. We keep an entitycollection of 'to be deleted' entities in each collection, then delete them in the DAL on update of the graph.
I'm still a bit confused about the concept of "hiding a relationship," though. In the documentation it looks like hiding a relationship is just supposed to be used to clean up the code a bit by not displaying relations you would never use. But from this experience it appears that hiding a relationship has a much greater effect by disabling the PK-FK syncing. I guess it just seems strange that I could have TableB TableA M:1 TableA TableB 1:N (hidden relationship)
and when I add a new TableBEntity, attach a TableAEntity to it, then save TableBEntity, that I would get an error because the TableAEntity 'cannot see' TableBEntity, even though TableBEntity has a reference to TableAEntity, and not only a reference, but a relationship!
Why that is, is explained above so I won't re-state it here. It's about the 2 parts of a relationship, so please re-read that to see why A can't sync with B even though B can see A.
Hiding relations was added mainly for m:n relations which weren't necessary. A couple of times we've considered removing the feature to hide one side of a relation for non-m:n relations and we're still considering that for future versions as it doesn't bring anything valuable, just more questions.