0

Situation

Assume:

  • NHibernate 4
  • A parent/child relationship (one-to-many), uni-directional mapping from parent to child
  • Inverse(false), i.e. the parent is responsible for the association
  • Cascade.All from parent to child
  • Versioning (database column that the DB auto-increments when record is updated) enabled for both parent and child (optimistic locking)

Observed behavior:

  1. Open session and transaction.
  2. Load some parent object together with its child collection via some query.
  3. Add a newly created child to the child collection.
  4. SaveOrUpdate the parent.
  5. NHibernate first adds the child to the database, giving it a version.
  6. It then takes care of the parent's association (because it is responsible for it) and updates the foreign key of the child (thank god it was nullable...), increasing the child's version. However, it does not update the child in the session.
  7. Finally it applies cascading and also calls AddOrUpdate on the child. Only problem: It has an outdated version of it in the session, causing a ConcurrencyException to be thrown. NHibernate is shooting itself in the foot.

Question

Why does NHibernate not update the child objects in the session when it applies the logic to update the association? What is the rationale behind it? It seems to me that the only reasonable way to model parent/child with versioning is to use Inverse(true).

Clarification: I am not interested in any workaround and/or more appropriate configuration. I know about those. I am only interested in the reasoning behind NHibernate's behavior in the described scenario.

Edit: The main problem seems to be (imo) that NHibernate doesn't fully control the versioning by itself. Rather, it let's the database take care of it (in my configuration at least). Unfortunately, this doesn't play well together, because NHibernate does not anticipate a version change in the client when it updates the association to it, because from a higher level view, the association belongs to the parent and therefore no change in the child is expected. This is at least a possible rationale behind it.

domin
  • 1,192
  • 1
  • 7
  • 28
  • In #6 above, you say '...increasing its version.' Which entity is getting its version changed in that step? The parent? – David Osborne Jun 13 '17 at 11:44
  • @DavidOsborne: The child entity's version. That's because the foreign key of the child was changed. – domin Jun 13 '17 at 13:03
  • If you wish someone to study what is going on in your case and whether there is a good rational behind that, or a bug on your side or on NHibernate side, or just an unfortunate unsupported combination of features vs technologies actually used, a [mcve] is a must: class definition, mappings, database used, software versions used, tested code with those and actually reproducing your concerns. Otherwise you are mostly up to hoping someone having already encounter the same case will come here and recognizes that is likely his same case, ... – Frédéric Jun 14 '17 at 10:47
  • A search on "one-to-many not inverse version child optimistic failure" on a well known search engine yields a question which looks as a [near dup](/q/13869948/1178314). – Frédéric Jun 14 '17 at 11:05
  • @Frédéric: Actually, I intentionally didn't want to bloat the question with the details of a working example, because "complete and minimal" is in this case not really "short" anymore. ;) I hoped for someone to know about the problem or the reason behind the behavior (depending on whether or not it should be considered a problem). I think DavidOsborne's answer is pretty good in that regard. Also thanks for the link. It is too bloated for me to read in detail, but it seems to be exactly that problem. I didn't anticipate that something so fundamental could still be buggy in NHib. – domin Jun 14 '17 at 11:25
  • Maybe because it is fundamental for you and likely a bunch of other people, but not for the majority of users. They may just not need to restrict their model in the same way than yours. If this is actually a bug (or design flaw, lack, whatever) but none of the concerned people takes the time to fill a good report or even tries to fix it themselves (that is open-source after all, [read here](https://github.com/nhibernate/nhibernate-core/blob/master/CONTRIBUTING.md)), no wonders it stays here. – Frédéric Jun 14 '17 at 11:40

2 Answers2

2

NHibernate is shooting itself in the foot.

This is unlikely. Your use case is quite typical and NH is a mature framework. You might've found a bug, but it's more likely that there's a problem with the mapping or the state of the object graph when NH tries to persist it.

Why does NHibernate not update the child objects in the session when it applies the logic to update the association?

I think this is because a change to the association only impacts the parent. If there's no reciprocal reference, nothing about the child, from an entity perspective, has changed.

However, it does not update the child in the session.

What changes were you anticipating? The child is now linked to the parent but this link is modelled as a part of the parent's responsibilities. I suppose this might be viewed differently if the child holds a reference to its parent but you've haven't said whether this is the case in your question.

David Osborne
  • 6,436
  • 1
  • 21
  • 35
  • The child does not hold a reference to the parent. But it wouldn't matter I think. By updating the association to the child, NHibernate has to change the child's foreign key, causing the concurrency version of the child (stored in some column of the child's db record) to be increased, therefore making the entity in the session outdated. From the configuration, it should be aware of that and also update the child in the session (basically just update the version field there). – domin Jun 13 '17 at 13:08
  • __"[...] because a change to the association only impacts the parent. If there's no reciprocal reference, nothing about the child, from an entity perspective, has changed."__ I like this statement, because it somehow makes sense. However, I don't see how the existence of a back-reference from child to parent would change the situation. Still, nothing has changed about the child from an *entity perspective*, because the owner of the association is still the parent, right? – domin Jun 14 '17 at 04:14
  • What I was getting at was that if the child class holds a reference to its parent, then there is an 'entity level' change to the child that is observable above the persistence layer. For me, the linking of the parent to its children via a database mechanism is something I would like to be unaware of if I'm working at the entity level. What you may have uncovered is that NH isn't working this way and is treating any persistence level changes as 'versionable'. Have you considered looking into the source? It can be very useful. – David Osborne Jun 14 '17 at 08:34
2

#6 bold part could be a bug, if you mean the children version is not retrieved and set up to date on them. Check https://nhibernate.jira.com, and if nothing, eventually report it with a complete test case. At least something like a MCVE. I am not trying to reproduce your case because your question let too much coding work up to who would like to check what is happening with your scenario. I do not even know which versioning mechanism are you using: they are many, with different behaviors.
(Following your edit, it seems to be some counter handled db side. But if you wish someone to study what is going on in your case and whether there is a good rational behind that, or a bug on your side or on NHibernate side, or just an unfortunate unsupported combination of features vs technologies actually used, a MCVE is still a must: class definition, mappings, database used, software versions used, tested code with those and actually reproducing your concerns.)

Now there are things you can do: why letting the parent be "responsible" of the children? That has nothing to do with cascading, and you may actually need only cascading. This is likely the case if your relation is bidirectional, having the children mapping back to their parent. More details here.

Otherwise, if your relation is uni-directional, and if having detached children does not need to be supported, you should set the key as non-nullable and mandates no updates from parent. More details here. This will do the job with set, but will creates issues with list such as no index updates and holes appearing in the list.

Frédéric
  • 9,364
  • 3
  • 62
  • 112
  • Maybe it is a bug, but I am so new to NHibernate that I don't yet feel confident enough about it. Maybe there is some misunderstanding on my side, hence this question. But when I am convinced that it might really be a bug, I will file a ticket. – domin Jun 14 '17 at 04:12