Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ISession.IsDirty() method immutable operation #1413

Open
bahusoid opened this issue Nov 1, 2017 · 19 comments
Open

Make ISession.IsDirty() method immutable operation #1413

bahusoid opened this issue Nov 1, 2017 · 19 comments

Comments

@bahusoid
Copy link
Member

bahusoid commented Nov 1, 2017

ISession.IsDirty() method description:

Does this ISession contain any changes which must be synchronized with the database? Would any SQL be executed if we flushed this session?

So expected behavior: method should not persist any entities (work in read-only mode)

And current behavior: It's very unexpected that this call can lead to data modifications in DB:
all cascaded entities are saved (at least all with Identity ID lead to DB inserts - very unexpected and dangerous behavior)
Edit: that point is fixed in 5.4 by #2752, but the session state can still be mutated by IsDirty: this needs still to be fixed.

My use case for using this ISession.IsDirty() is the following:

var entity = LoadEntity();
//Do some modifications to entity like entity.Children.Add(child); 
//which is mapped with Cascade.All; Also child Id is Identity generated by DB
entity.Children.Add(CreateNewChild());

//... And at the end of the request:
if(session.IsDirty())
{
	//do some resource consuming stuff like open DTC transaction or something else that needs to be done only if database modifications are requried
	using(var dtcTransaction = OpenDTCTransaction())
	{
		session.Flush();
		//...do some stuff with legacy VB objects
	}
}

With current implementation I can't use IsDirty() as it will save child entity.Children elements without transaction context - very dangerous and unexpected.
See test case #1414

@fredericDelaporte
Copy link
Member

Seems related to NH-2727 and NH-2136 which were closed as "Not an issue" without much explanations.
About NH-2136, the case happens with identity generator. Maybe there is some good technical constraint mandating this behavior with identity, but it should then be documented on IsDirty (and added to the list of reasons it is not recommended to use identity for generating ids).

@hazzik
Copy link
Member

hazzik commented Nov 2, 2017

Maybe some information is in the mailing lists (I’m away sleeping, so could not check now)

@hazzik
Copy link
Member

hazzik commented Nov 2, 2017

Read the linked issues. I don’t know why this is an “expected behavior”, @fabiomaulo. Could you please elaborate? I agree that the behavior should be as described by @bahusoid.

@fabiomaulo
Copy link
Member

I can't remember which was the discussion around this matter... let me check...

@fabiomaulo
Copy link
Member

fabiomaulo commented Nov 2, 2017

Ok... IIRC things, in these cases, are wired with flush mode. In NH-2727 Ricardo said he is changing the entity during saveorupdate (probably with a listener) so the session becomes dirty. IMO as he have implemented a custom-listener to do something during saveorupdate he should change even DefaultDirtyCheckEventListener.
The second issue is easier... the usage of Identity strategy for the POID is the cause... the only way to know an ID is going to DB. hmmm, I think I wrote a post some years ago, maybe more than one:
http://fabiomaulo.blogspot.com.ar/2008/12/identity-never-ending-story.html
http://fabiomaulo.blogspot.com.ar/2009/02/nh210-generators-behavior-explained.html
There should be some others... Identity is a very ugly beast.
BTW: Perhaps the behavior of the default listener can be changed, fixing Ricardo's issue, even if we have to have extremely careful because somebody may change something, in the graph of the loaded objects, using events wired to the session lifecycle so we may have a fake state (the IsDirty throw a false but something will be saved on flush).

@fredericDelaporte
Copy link
Member

The point with identity is, why should IsDirty knows the id? If it is mandatory for checking dirtiness, so be it, but otherwise it should avoid triggering the id generation (which in the case of identity triggers the whole insert).

About NH-2727, it looks to me as boiling down to this question: is it legit for IsDirty to raise SaveOrUpdate events?

By the way this issue 1413 is indeed two issues: the many-to-one failure and the id generation triggered by IsDirty (which is maybe bound to the fact it raises SaveOrUpdate, I have not checked anything in code yet about all that).

@fabiomaulo
Copy link
Member

Yes Federic, in general terms something called IsDirty should not call something called SaveOrUpdate... probably there is a short way to check and should be re-wrote.
The dirty check is not so easy when you work with collections of objects graphs where you have to deal with the cascade behavior...anyway a dirty collection become a dirty session.

@bahusoid

This comment has been minimized.

@fredericDelaporte

This comment has been minimized.

@bahusoid

This comment has been minimized.

@hazzik
Copy link
Member

hazzik commented Nov 3, 2017

So, it seems that this is the same as NH-2136, except it happens to anything regardless of the type of id generator.

@hazzik

This comment has been minimized.

@bahusoid

This comment has been minimized.

@bahusoid bahusoid changed the title Make ISession.IsDirty() method safe, effective and useful Make ISession.IsDirty() method immutable operation Nov 19, 2017
hazzik pushed a commit to bahusoid/nhibernate-core that referenced this issue Nov 5, 2018
…enerator

Depending on the db, an insert may be triggered (SQL-Server) or not (Oracle).
Tested with Statistics rather than SqlLogSpy: it avoids parsing the SQL for distinguishing between
select (done with Oracle over the sequence) and insert (done with SQL-Server).

See nhibernate#1413
hazzik pushed a commit to bahusoid/nhibernate-core that referenced this issue Nov 5, 2018
Depending on the db, an insert may be triggered (SQL-Server) or not (Oracle).

See nhibernate#1413
hazzik pushed a commit to bahusoid/nhibernate-core that referenced this issue Nov 5, 2018
Gankov added a commit to QualitySolution/QSProjects that referenced this issue Mar 17, 2020
Есть давно известный баг nhibernate/nhibernate-core#1413 в том что при вызове IsDirty хибернейт может сохранять изменения в базу. Частично это решается открытием транзакции. которая откатывается при отмене. Но если какие то из полей стоят not-null то проблема записи при проверке не исчезает. Поэтому реализована собственная проверка на изменения. Откуда взять код указано в начале класса.
@hazzik hazzik removed the p: Minor label May 10, 2021
@hazzik
Copy link
Member

hazzik commented May 10, 2021

This is actually p: Major due the fact if there is no transaction it will perform a real flush to the database, which would be considered as a manual flush by the core.

hazzik added a commit to hazzik/nhibernate-core that referenced this issue May 10, 2021
… flush

The default cascade for it was SaveUpdate (via base AbstractFlushingEventListener) which triggered
insertion of the entities with post-insert style ID generators (eg. identity). Persist on the other hand will
make sure that insertion of the entity is delyed until flush is called.

Fixes nhibernate#1413
hazzik added a commit to hazzik/nhibernate-core that referenced this issue May 15, 2021
hazzik added a commit that referenced this issue May 15, 2021
… avoid flushing the session (#2752)

The default cascade for it was SaveUpdate (via base AbstractFlushingEventListener) which triggered
insertion of the entities with post-insert style ID generators (eg. identity). Persist on the other hand will
make sure that insertion of the entity is delayed until flush is called.

Related to #1413
@trivalik
Copy link

Does this happen also in older NHibernate 3.x?

I am right now struggling with a huge performance gap after migrate NHibernate from 3.1 to 5.3.16/5.4.2 on calling IsDirty, Since the call is near after every change in program, it lead to a less fluent work flow.

@fredericDelaporte
Copy link
Member

fredericDelaporte commented May 23, 2023

I am not sure whether this bug applies to such an old version or not, but is sounds likely to me. IsDirty is implemented almost like a Flush theoretically without the updates sent to the database. That is the reason for the bug in some special cases. That implementation has not changed much as far as I know.

About the performance loss for your application, if you could pinpoint what causes it (like profiling sql queries and seeing if there are more of them issued on IsDirty calls, or other things), that would help seeing if that is something needing a fix, and then please report it as a new issue with detailed information of how to reproduce it.

About the principle of calling frequently IsDirty, that is anyway a huge source of performance loss. The basic mechanism for dirty checking in NHibernate has not changed since its first releases: at worst the session iterates all entities it holds and compares their current state with the state they were having at load time. That is a quite heavy mechanism.
(Hibernate (Java counterpart) has some extension points allowing to use different dirty checking mechanisms, but they have not yet been ported to NHibernate.)
With such a mechanism, it is better to use dirty checking the least possible. (Actually I had never used it in applications I had worked on.)
Especially, there is no point in coding something like if (session.IsDrity()) session.Flush();: flush anyway performs a dirty check itself, so such a code just triggers a double dirty check when the session is dirty, without improving the performances of the "not dirty case". A variation of such an anti-pattern can also be if (session.IsDrity()) session.Commit(); else session.Rollback();: just commit instead. There is nothing to rollback anyway if the session is not dirty, so committing instead is equivalent.

@trivalik
Copy link

trivalik commented May 24, 2023

I will keep that in mind. That I try to make an example in my spare time. I will post at least the profiling result in a new issue.

@fredericDelaporte
Copy link
Member

I forgot about #2752: is the current description of the issue still relevant? I mean, may we still have entities inserted into the DB on dirty checking? Normally, with #2752, we only have at worst new entities added into the session, but not sent to the database.

@bahusoid
Copy link
Member Author

I forgot about #2752: is the current description of the issue still relevant?

No DB modifications but session state is still modified (see #2752 (comment))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants