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

Bugfix for Issue 3622: ISession.Refresh does not update lazy properties #3642

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1087,6 +1087,41 @@ void AssertPersons(List<Person> results, bool fetched)
}
}

[Test]
public async Task TestRefreshRemovesLazyLoadedPropertiesAsync()
{
using (var outerSession = OpenSession())
{
const string query = "from Person fetch Image where Id = 1";
const string namePostFix = "_MODIFIED";
const int imageLength = 1000;

Person outerPerson = await (outerSession.CreateQuery(query).UniqueResultAsync<Person>());

Assert.That(outerPerson.Name.EndsWith(namePostFix), Is.False); // Normal property
Assert.That(outerPerson.Image.Length, Is.EqualTo(1)); // Lazy Property

// Changing the properties of the person in a different sessions
using (var innerSession = OpenSession())
{
var transaction = innerSession.BeginTransaction();

Person innerPerson = await (innerSession.CreateQuery(query).UniqueResultAsync<Person>());
innerPerson.Image = new byte[imageLength];
innerPerson.Name += namePostFix;
await (innerSession.UpdateAsync(innerPerson));

await (transaction.CommitAsync());
}

// Refreshing the person in the outer session
await (outerSession.RefreshAsync(outerPerson));

Assert.That(outerPerson.Name.EndsWith(namePostFix), Is.True); // Value has changed
Assert.That(outerPerson.Image.Length, Is.EqualTo(imageLength)); // This is still the old value
}
}

private static Person GeneratePerson(int i, Person bestFriend)
{
return new Person
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,41 @@ void AssertPersons(List<Person> results, bool fetched)
}
}

[Test]
public void TestRefreshRemovesLazyLoadedProperties()
{
using (var outerSession = OpenSession())
{
const string query = "from Person fetch Image where Id = 1";
const string namePostFix = "_MODIFIED";
const int imageLength = 1000;

Person outerPerson = outerSession.CreateQuery(query).UniqueResult<Person>();

Assert.That(outerPerson.Name.EndsWith(namePostFix), Is.False); // Normal property
Assert.That(outerPerson.Image.Length, Is.EqualTo(1)); // Lazy Property

// Changing the properties of the person in a different sessions
using (var innerSession = OpenSession())
{
var transaction = innerSession.BeginTransaction();

Person innerPerson = innerSession.CreateQuery(query).UniqueResult<Person>();
innerPerson.Image = new byte[imageLength];
innerPerson.Name += namePostFix;
innerSession.Update(innerPerson);

transaction.Commit();
}

// Refreshing the person in the outer session
outerSession.Refresh(outerPerson);

Assert.That(outerPerson.Name.EndsWith(namePostFix), Is.True); // Value has changed
Assert.That(outerPerson.Image.Length, Is.EqualTo(imageLength)); // This is still the old value
}
}

private static Person GeneratePerson(int i, Person bestFriend)
{
return new Person
Expand Down
11 changes: 6 additions & 5 deletions src/NHibernate/Async/Loader/Loader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,8 @@ private async Task<object> InstanceNotYetLoadedAsync(DbDataReader dr, int i, ILo

ILoadable concretePersister = await (GetConcretePersisterAsync(dr, i, persister, key.Identifier, session, cancellationToken)).ConfigureAwait(false);

if (optionalObjectKey != null && key.Equals(optionalObjectKey))
bool useOptionalObject = optionalObjectKey != null && key.Equals(optionalObjectKey);
if (useOptionalObject)
{
// its the given optional object
obj = optionalObject;
Expand All @@ -773,8 +774,8 @@ private async Task<object> InstanceNotYetLoadedAsync(DbDataReader dr, int i, ILo
// (but don't yet initialize the object itself)
// note that we acquired LockMode.READ even if it was not requested
LockMode acquiredLockMode = lockMode == LockMode.None ? LockMode.Read : lockMode;
await (LoadFromResultSetAsync(dr, i, obj, concretePersister, key, acquiredLockMode, persister, session, cancellationToken)).ConfigureAwait(false);

await (LoadFromResultSetAsync(dr, i, obj, concretePersister, key, acquiredLockMode, persister, session, useOptionalObject, cancellationToken)).ConfigureAwait(false);
// materialize associations (and initialize the object) later
hydratedObjects.Add(obj);

Expand Down Expand Up @@ -886,7 +887,7 @@ internal static async Task UpdateCacheForEntityAsync(
/// </summary>
private async Task LoadFromResultSetAsync(DbDataReader rs, int i, object obj, ILoadable persister, EntityKey key,
LockMode lockMode, ILoadable rootPersister,
ISessionImplementor session, CancellationToken cancellationToken)
ISessionImplementor session, bool lazyPropertiesAreUnfetched, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
object id = key.Identifier;
Expand All @@ -912,7 +913,7 @@ private async Task LoadFromResultSetAsync(DbDataReader rs, int i, object obj, IL

object rowId = persister.HasRowId ? rs[EntityAliases[i].RowIdAlias] : null;

TwoPhaseLoad.PostHydrate(persister, id, values, rowId, obj, lockMode, session);
TwoPhaseLoad.PostHydrate(persister, id, values, rowId, obj, lockMode, lazyPropertiesAreUnfetched, session);
}

/// <summary>
Expand Down
4 changes: 1 addition & 3 deletions src/NHibernate/Engine/EntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,7 @@ public object RowId
{
get { return rowId; }
}

// Since 5.3
[Obsolete("This property is not used and will be removed in a future version.")]

public bool LoadedWithLazyPropertiesUnfetched
{
get { return loadedWithLazyPropertiesUnfetched; }
Expand Down
2 changes: 0 additions & 2 deletions src/NHibernate/Engine/IPersistenceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,8 +173,6 @@ EntityEntry AddEntity(object entity, Status status, object[] loadedState, Entity
/// Generates an appropriate EntityEntry instance and adds it
/// to the event source's internal caches.
/// </summary>
// Since 5.3
[Obsolete("Use the AddEntry extension method instead")]
EntityEntry AddEntry(object entity, Status status, object[] loadedState, object rowId, object id, object version,
LockMode lockMode, bool existsInDatabase, IEntityPersister persister, bool disableVersionIncrement,
bool lazyPropertiesAreUnfetched);
Expand Down
2 changes: 0 additions & 2 deletions src/NHibernate/Engine/TwoPhaseLoad.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ public static partial class TwoPhaseLoad
/// to resolve any associations yet, because there might be other entities waiting to be
/// read from the JDBC result set we are currently processing
/// </summary>
// Since 5.3
[Obsolete("Use the overload without lazyPropertiesAreUnfetched parameter instead")]
public static void PostHydrate(IEntityPersister persister, object id, object[] values, object rowId, object obj, LockMode lockMode, bool lazyPropertiesAreUnfetched, ISessionImplementor session)
{
object version = Versioning.GetVersion(values, persister);
Expand Down
11 changes: 6 additions & 5 deletions src/NHibernate/Loader/Loader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,8 @@ private object InstanceNotYetLoaded(DbDataReader dr, int i, ILoadable persister,

ILoadable concretePersister = GetConcretePersister(dr, i, persister, key.Identifier, session);

if (optionalObjectKey != null && key.Equals(optionalObjectKey))
bool useOptionalObject = optionalObjectKey != null && key.Equals(optionalObjectKey);
if (useOptionalObject)
{
// its the given optional object
obj = optionalObject;
Expand All @@ -1163,8 +1164,8 @@ private object InstanceNotYetLoaded(DbDataReader dr, int i, ILoadable persister,
// (but don't yet initialize the object itself)
// note that we acquired LockMode.READ even if it was not requested
LockMode acquiredLockMode = lockMode == LockMode.None ? LockMode.Read : lockMode;
LoadFromResultSet(dr, i, obj, concretePersister, key, acquiredLockMode, persister, session);

LoadFromResultSet(dr, i, obj, concretePersister, key, acquiredLockMode, persister, session, useOptionalObject);
// materialize associations (and initialize the object) later
hydratedObjects.Add(obj);

Expand Down Expand Up @@ -1291,7 +1292,7 @@ internal static void UpdateCacheForEntity(
/// </summary>
private void LoadFromResultSet(DbDataReader rs, int i, object obj, ILoadable persister, EntityKey key,
LockMode lockMode, ILoadable rootPersister,
ISessionImplementor session)
ISessionImplementor session, bool lazyPropertiesAreUnfetched)
{
object id = key.Identifier;

Expand All @@ -1316,7 +1317,7 @@ private void LoadFromResultSet(DbDataReader rs, int i, object obj, ILoadable per

object rowId = persister.HasRowId ? rs[EntityAliases[i].RowIdAlias] : null;

TwoPhaseLoad.PostHydrate(persister, id, values, rowId, obj, lockMode, session);
TwoPhaseLoad.PostHydrate(persister, id, values, rowId, obj, lockMode, lazyPropertiesAreUnfetched, session);
}

private string[][] GetSubclassEntityAliases(int i, ILoadable persister)
Expand Down
9 changes: 8 additions & 1 deletion src/NHibernate/Tuple/Entity/PocoEntityTuplizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public override void AfterInitialize(object entity, ISessionImplementor session)
if (IsInstrumented)
{
var interceptor = _enhancementMetadata.ExtractInterceptor(entity);
if (interceptor == null)
if (interceptor == null || HasAnyInitializedLazyProperty(entity, session))
{
interceptor = _enhancementMetadata.InjectInterceptor(entity, session);
}
Expand All @@ -239,6 +239,13 @@ public override void AfterInitialize(object entity, ISessionImplementor session)
}
}

private static bool HasAnyInitializedLazyProperty(object entity, ISessionImplementor session)
{
IPersistenceContext persistenceContext = session.PersistenceContext;
EntityEntry entityEntry = persistenceContext.GetEntry(entity);
return entityEntry.LoadedWithLazyPropertiesUnfetched;
}

public override object GetPropertyValue(object entity, int i)
{
if (isBytecodeProviderImpl && optimizer?.AccessOptimizer != null)
Expand Down