Skip to content

Commit b6bdb66

Browse files
author
Thomas Alken
committed
Fixes issue 3622: ISession.Refresh updates initialized lazy properties
1 parent f5a7a1f commit b6bdb66

File tree

4 files changed

+70
-2
lines changed

4 files changed

+70
-2
lines changed

src/NHibernate.Test/FetchLazyProperties/FetchLazyPropertiesFixture.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,41 @@ void AssertPersons(List<Person> results, bool fetched)
10751075
}
10761076
}
10771077
}
1078+
1079+
[Test]
1080+
public void TestRefreshRemovesLazyLoadedProperties()
1081+
{
1082+
using (var outerSession = OpenSession())
1083+
{
1084+
const string query = "from Person fetch Image where Id = 1";
1085+
const string namePostFix = "_MODIFIED";
1086+
const int imageLength = 4711;
1087+
1088+
Person outerPerson = outerSession.CreateQuery(query).UniqueResult<Person>();
1089+
1090+
Assert.That(outerPerson.Name.EndsWith(namePostFix), Is.False); // Normal property
1091+
Assert.That(outerPerson.Image.Length, Is.EqualTo(1)); // Lazy Property
1092+
1093+
// Changing the properties of the person in a different sessions
1094+
using (var innerSession = OpenSession())
1095+
{
1096+
var transaction = innerSession.BeginTransaction();
1097+
1098+
Person innerPerson = innerSession.CreateQuery(query).UniqueResult<Person>();
1099+
innerPerson.Image = new byte[imageLength];
1100+
innerPerson.Name += namePostFix;
1101+
innerSession.Update(innerPerson);
1102+
1103+
transaction.Commit();
1104+
}
1105+
1106+
// Refreshing the person in the outer session
1107+
outerSession.Refresh(outerPerson);
1108+
1109+
Assert.That(outerPerson.Name.EndsWith(namePostFix), Is.True); // Value has changed
1110+
Assert.That(outerPerson.Image.Length, Is.EqualTo(imageLength)); // This is still the old value
1111+
}
1112+
}
10781113

10791114
private static Person GeneratePerson(int i, Person bestFriend)
10801115
{

src/NHibernate/Event/Default/DefaultRefreshEventListener.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ public virtual void OnRefresh(RefreshEvent @event, IDictionary refreshedAlready)
9797
}
9898

9999
EvictCachedCollections(persister, id, source.Factory);
100-
100+
101+
RefreshLazyProperties(persister, obj);
102+
101103
// NH Different behavior : NH-1601
102104
// At this point the entity need the real refresh, all elementes of collections are Refreshed,
103105
// the collection state was evicted, but the PersistentCollection (in the entity state)
@@ -142,5 +144,20 @@ private void EvictCachedCollections(IType[] types, object id, ISessionFactoryImp
142144
}
143145
}
144146
}
147+
148+
private static void RefreshLazyProperties(IEntityPersister persister, object obj)
149+
{
150+
if (obj == null)
151+
return;
152+
153+
// TODO: InstrumentationMetadata needs to be in IPersister
154+
var castedPersister = persister as AbstractEntityPersister;
155+
if (castedPersister?.InstrumentationMetadata?.EnhancedForLazyLoading == true)
156+
{
157+
var interceptor = castedPersister.InstrumentationMetadata.ExtractInterceptor(obj);
158+
// The list of initialized lazy fields have to be cleared in order to refresh them from the database.
159+
interceptor?.ClearInitializedLazyFields();
160+
}
161+
}
145162
}
146163
}

src/NHibernate/Intercept/AbstractFieldInterceptor.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
34
using Iesi.Collections.Generic;
45
using NHibernate.Engine;
56
using NHibernate.Persister.Entity;
@@ -21,7 +22,8 @@ public abstract class AbstractFieldInterceptor : IFieldInterceptor
2122
private readonly HashSet<string> loadedUnwrapProxyFieldNames = new HashSet<string>();
2223
private readonly string entityName;
2324
private readonly System.Type mappedClass;
24-
25+
private readonly string[] originalUninitializedFields;
26+
2527
[NonSerialized]
2628
private bool initializing;
2729
private bool isDirty;
@@ -34,6 +36,7 @@ protected internal AbstractFieldInterceptor(ISessionImplementor session, ISet<st
3436
this.entityName = entityName;
3537
this.mappedClass = mappedClass;
3638
this.uninitializedFieldsReadOnly = uninitializedFields != null ? new ReadOnlySet<string>(uninitializedFields) : null;
39+
this.originalUninitializedFields = uninitializedFields != null ? uninitializedFields.ToArray() : null;
3740
}
3841

3942
#region IFieldInterceptor Members
@@ -209,5 +212,16 @@ public ISet<string> GetUninitializedFields()
209212
{
210213
return uninitializedFieldsReadOnly ?? CollectionHelper.EmptySet<string>();
211214
}
215+
216+
public void ClearInitializedLazyFields()
217+
{
218+
if (this.originalUninitializedFields == null)
219+
return;
220+
221+
foreach (var originalUninitializedField in this.originalUninitializedFields)
222+
{
223+
this.uninitializedFields.Add(originalUninitializedField);
224+
}
225+
}
212226
}
213227
}

src/NHibernate/Intercept/IFieldInterceptor.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public interface IFieldInterceptor
4141

4242
/// <summary> Get the MappedClass (field container).</summary>
4343
System.Type MappedClass { get; }
44+
45+
void ClearInitializedLazyFields();
4446
}
4547

4648
public static class FieldInterceptorExtensions

0 commit comments

Comments
 (0)