-
Notifications
You must be signed in to change notification settings - Fork 934
Join-fetching with property-ref results in select n+1 #1492
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
Conversation
src/NHibernate/Loader/Loader.cs
Outdated
@@ -972,6 +972,29 @@ private void CheckVersion(int i, IEntityPersister persister, object id, object e | |||
entry.LockMode = lockMode; | |||
} | |||
} | |||
|
|||
// #1226: If it is already loaded and can be loaded from an association with a property ref, make |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this please be extracted into an own method?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@fredericDelaporte why not do it in the original PR? It seems to have "changes from contributors" enabled. |
Yes but I have seen on some other PR that some people have difficulties handling forced pushes. So unless the contributor has already shown by its action on his PR that it is not a trouble for him, I now avoid doing forced pushes on their PR. |
@fredericDelaporte Yes, force pushing on someone else's PR should only be done after communicating with the owner about it. |
* Fixes nhibernate#1226 (NH-2534)
b443a86
to
00b36c0
Compare
@fredericDelaporte, @kfedorchenko can the CacheByUniqueKey logic be unifed, eg. called this way: //If the object is already loaded, return the loaded one
obj = session.GetEntityUsingInterceptor(key);
if (obj != null)
{
//its already loaded so dont need to hydrate it
InstanceAlreadyLoaded(rs, i, persisters[i], key, obj, lockModes[i], session);
}
else
{
obj = InstanceNotYetLoaded(rs, i, persisters[i], key, lockModes[i], descriptors[i].RowIdAlias, optionalObjectKey, optionalObject, hydratedObjects, session);
}
CacheByUniqueKey(i, persisters[i], obj, session); And remove this logic from (I don't know if this is possible or not, it just a suggestion) |
No, there is a trap here: in the |
I've discovered this "trap". this code works: obj = session.GetEntityUsingInterceptor(key);
var persister = persisters[i];
object[] values;
if (obj != null)
{
//its already loaded so dont need to hydrate it
InstanceAlreadyLoaded(rs, i, persister, key, obj, lockModes[i], session);
values = persister.GetPropertyValues(obj);
CacheByUniqueKey(i, persister, obj, session, values);
}
else
{
obj = InstanceNotYetLoaded(rs, i, persister, key, lockModes[i], descriptors[i].RowIdAlias, optionalObjectKey, optionalObject, hydratedObjects, session);
var entry = session.PersistenceContext.GetEntry(obj);
values = entry.LoadedState;
}
CacheByUniqueKey(i, persister, obj, session, values); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor code issues, otherwise looks good.
src/NHibernate/Loader/Loader.cs
Outdated
{ | ||
// #1226: If it is already loaded and can be loaded from an association with a property ref, make | ||
// sure it is also cached by its unique key. | ||
var ukName = OwnerAssociationTypes?[i]?.RHSUniqueKeyPropertyName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we unfold ?
operator here? It looks unreadable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just null propagation all the way. Original code was:
IAssociationType[] ownerAssociationTypes = OwnerAssociationTypes;
if (ownerAssociationTypes != null && ownerAssociationTypes[i] != null)
{
string ukName = ownerAssociationTypes[i].RHSUniqueKeyPropertyName;
Would you rather have:
var ownerAssociationTypes = OwnerAssociationTypes;
if (ownerAssociationTypes == null)
return;
var ukName = ownerAssociationTypes[i]?.RHSUniqueKeyPropertyName;
using (var tx = session.BeginTransaction()) | ||
{ | ||
session.Delete("from Account"); | ||
session.Delete("from Bank"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CreateQuery("delete ...").ExecuteUpdate()
?
|
||
using (var session = OpenSession()) | ||
{ | ||
using (var tx = session.BeginTransaction()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please align nested usings:
using(...)
using(...)
{
}
|
||
using (var session = OpenSession()) | ||
{ | ||
using (var tx = session.BeginTransaction()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same
if (ukValue == null) | ||
return; | ||
var type = persister.PropertyTypes[index]; | ||
var euk = new EntityUniqueKey(persister.EntityName, ukName, ukValue, type, session.Factory); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LoadFromRow's version caches by rootPersister.EntityName. Please add a test where the referenced entity is a hierarchical object.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here persister
is rootPersister
. Non-root persister
is a thing existing only inside LoadFromResultSet
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok
But it causes, in the already loaded case, to get all the entity properties from its tuplizer even if there is no unique key, which is quite heavier than getting only one property when there is an unique key. I would rather pass |
using (var session = OpenSession()) | ||
using (var tx = session.BeginTransaction()) | ||
{ | ||
// Bug only occurs if the Banks are already in the session cache. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the old code has changed, could this case please be checked as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You mean, checking eager fetching works for Bank
not already in session too? It is likely this is already tested somewhere else, but why not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Fix key type mismatch in EntityUniqueKey EntityUniqueKey was assuming the value to be always semi-resolved (to be the identifier instead of the entity for an entity type). It was always computing the value hashcode with the semi-resolved type, which is the identifier type for an entity. But this has changed with nhibernate#1492, which caches unique keys also for already fully loaded entities. The value hashcode must then be computed with the entity type, which accounts for proxies. Otherwise the proxy loading may get triggered while the code is already loading the proxy, which results in a stack overflow. Add test case adapted from Michael Estermann repro Fix nhibernate#1645 Co-authored-by: Michael Estermann <michael.estermann@bbv.ch>
Fix key type mismatch in EntityUniqueKey EntityUniqueKey was assuming the value to be always semi-resolved (to be the identifier instead of the entity for an entity type). It was always computing the value hashcode with the semi-resolved type, which is the identifier type for an entity. But this has changed with #1492, which caches unique keys also for already fully loaded entities. The value hashcode must then be computed with the entity type, which accounts for proxies. Otherwise the proxy loading may get triggered while the code is already loading the proxy, which results in a stack overflow. Add test case adapted from Michael Estermann repro Fix #1645 Co-authored-by: Michael Estermann <michael.estermann@bbv.ch>
This is a rebase of #1460, with async regen, squashed, as it was asked here.