Skip to content

Commit ac76724

Browse files
committed
Partially port Hibernate's current field interceptor mechanism
1 parent dfae76d commit ac76724

13 files changed

+282
-29
lines changed

src/NHibernate.Test/Async/GhostProperty/GhostPropertyFixture.cs

+48
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ protected override void OnSetUp()
4747
Id = 1
4848
};
4949
s.Persist(wireTransfer);
50+
var creditCard = new CreditCard
51+
{
52+
Id = 2
53+
};
54+
s.Persist(creditCard);
5055
s.Persist(new Order
5156
{
5257
Id = 1,
@@ -89,6 +94,49 @@ public async Task CanGetActualValueFromLazyManyToOneAsync()
8994
}
9095
}
9196

97+
[Test]
98+
public async Task CanGetInitializedLazyManyToOneAfterClosedSessionAsync()
99+
{
100+
Order order;
101+
Payment payment;
102+
103+
using (var s = OpenSession())
104+
{
105+
order = await (s.GetAsync<Order>(1));
106+
payment = order.Payment; // Initialize Payment
107+
}
108+
109+
Assert.That(order.Payment, Is.EqualTo(payment));
110+
}
111+
112+
[Test]
113+
public async Task SetUninitializedProxyShouldResetPropertyInitializationAsync()
114+
{
115+
using (var s = OpenSession())
116+
{
117+
var order = await (s.GetAsync<Order>(1));
118+
Assert.That(order.Payment is WireTransfer, Is.True); // Load property
119+
Assert.That(NHibernateUtil.IsPropertyInitialized(order, "Payment"), Is.True);
120+
order.Payment = await (s.LoadAsync<Payment>(2));
121+
Assert.That(NHibernateUtil.IsPropertyInitialized(order, "Payment"), Is.False);
122+
}
123+
}
124+
125+
[Test]
126+
public async Task SetInitializedProxyShouldNotResetPropertyInitializationAsync()
127+
{
128+
using (var s = OpenSession())
129+
{
130+
var order = await (s.GetAsync<Order>(1));
131+
var payment = await (s.LoadAsync<Payment>(2));
132+
Assert.That(order.Payment is WireTransfer, Is.True); // Load property
133+
Assert.That(NHibernateUtil.IsPropertyInitialized(order, "Payment"), Is.True);
134+
await (s.GetAsync<Payment>(2)); // Load the uninitialized payment
135+
order.Payment = payment;
136+
Assert.That(NHibernateUtil.IsPropertyInitialized(order, "Payment"), Is.True);
137+
}
138+
}
139+
92140
[Test]
93141
public async Task WillNotLoadGhostPropertyByDefaultAsync()
94142
{

src/NHibernate.Test/Async/LazyOneToOne/LazyOneToOneTest.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ public async Task LazyAsync()
7878

7979
s = OpenSession();
8080
t = s.BeginTransaction();
81-
p = await (s.GetAsync<Person>("Gavin"));
82-
Assert.That(!NHibernateUtil.IsPropertyInitialized(p, "Employee"));
81+
p = await (s.GetAsync<Person>("Gavin")); // The default loader will fetch the employee
82+
Assert.That(NHibernateUtil.IsPropertyInitialized(p, "Employee"));
8383

8484
Assert.That(p.Employee.Person, Is.SameAs(p));
8585
Assert.That(NHibernateUtil.IsInitialized(p.Employee.Employments));
@@ -95,4 +95,4 @@ public async Task LazyAsync()
9595
s.Close();
9696
}
9797
}
98-
}
98+
}

src/NHibernate.Test/GhostProperty/GhostPropertyFixture.cs

+48
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ protected override void OnSetUp()
3636
Id = 1
3737
};
3838
s.Persist(wireTransfer);
39+
var creditCard = new CreditCard
40+
{
41+
Id = 2
42+
};
43+
s.Persist(creditCard);
3944
s.Persist(new Order
4045
{
4146
Id = 1,
@@ -84,6 +89,49 @@ public void CanGetActualValueFromLazyManyToOne()
8489
}
8590
}
8691

92+
[Test]
93+
public void CanGetInitializedLazyManyToOneAfterClosedSession()
94+
{
95+
Order order;
96+
Payment payment;
97+
98+
using (var s = OpenSession())
99+
{
100+
order = s.Get<Order>(1);
101+
payment = order.Payment; // Initialize Payment
102+
}
103+
104+
Assert.That(order.Payment, Is.EqualTo(payment));
105+
}
106+
107+
[Test]
108+
public void SetUninitializedProxyShouldResetPropertyInitialization()
109+
{
110+
using (var s = OpenSession())
111+
{
112+
var order = s.Get<Order>(1);
113+
Assert.That(order.Payment is WireTransfer, Is.True); // Load property
114+
Assert.That(NHibernateUtil.IsPropertyInitialized(order, "Payment"), Is.True);
115+
order.Payment = s.Load<Payment>(2);
116+
Assert.That(NHibernateUtil.IsPropertyInitialized(order, "Payment"), Is.False);
117+
}
118+
}
119+
120+
[Test]
121+
public void SetInitializedProxyShouldNotResetPropertyInitialization()
122+
{
123+
using (var s = OpenSession())
124+
{
125+
var order = s.Get<Order>(1);
126+
var payment = s.Load<Payment>(2);
127+
Assert.That(order.Payment is WireTransfer, Is.True); // Load property
128+
Assert.That(NHibernateUtil.IsPropertyInitialized(order, "Payment"), Is.True);
129+
s.Get<Payment>(2); // Load the uninitialized payment
130+
order.Payment = payment;
131+
Assert.That(NHibernateUtil.IsPropertyInitialized(order, "Payment"), Is.True);
132+
}
133+
}
134+
87135
[Test]
88136
public void WillNotLoadGhostPropertyByDefault()
89137
{

src/NHibernate.Test/LazyOneToOne/LazyOneToOneTest.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ public void Lazy()
6767

6868
s = OpenSession();
6969
t = s.BeginTransaction();
70-
p = s.Get<Person>("Gavin");
71-
Assert.That(!NHibernateUtil.IsPropertyInitialized(p, "Employee"));
70+
p = s.Get<Person>("Gavin"); // The default loader will fetch the employee
71+
Assert.That(NHibernateUtil.IsPropertyInitialized(p, "Employee"));
7272

7373
Assert.That(p.Employee.Person, Is.SameAs(p));
7474
Assert.That(NHibernateUtil.IsInitialized(p.Employee.Employments));
@@ -84,4 +84,4 @@ public void Lazy()
8484
s.Close();
8585
}
8686
}
87-
}
87+
}

src/NHibernate/Bytecode/UnwrapProxyPropertiesMetadata.cs

+5
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ public UnwrapProxyPropertiesMetadata(
4848
public IEnumerable<UnwrapProxyPropertyDescriptor> UnwrapProxyPropertyDescriptors =>
4949
_unwrapProxyPropertyDescriptors?.Values ?? Enumerable.Empty<UnwrapProxyPropertyDescriptor>();
5050

51+
/// <summary>
52+
/// Get the index of the property in terms of its position in the entity persister which is mapped as proxy="no-proxy"
53+
/// </summary>
54+
/// <param name="propertyName">The propery name.</param>
55+
/// <returns>The index of the property.</returns>
5156
public int GetUnwrapProxyPropertyIndex(string propertyName)
5257
{
5358
if (!_unwrapProxyPropertyDescriptors.TryGetValue(propertyName, out var descriptor))

src/NHibernate/Intercept/AbstractFieldInterceptor.cs

+22-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using Iesi.Collections.Generic;
44
using NHibernate.Engine;
5+
using NHibernate.Persister.Entity;
56
using NHibernate.Proxy;
67
using NHibernate.Util;
78

@@ -113,16 +114,27 @@ public object Intercept(object target, string fieldName, object value, bool sett
113114
uninitializedFields.Remove(fieldName);
114115
}
115116

116-
if (IsUninitializedAssociation(fieldName))
117+
if (!unwrapProxyFieldNames.Contains(fieldName))
118+
{
119+
return value;
120+
}
121+
122+
if (!value.IsProxy() || NHibernateUtil.IsInitialized(value))
117123
{
118124
loadedUnwrapProxyFieldNames.Add(fieldName);
119125
}
126+
else
127+
{
128+
loadedUnwrapProxyFieldNames.Remove(fieldName);
129+
}
130+
131+
return value;
120132
}
121133

122134
if (IsInitializedField(fieldName))
123135
{
124136
if (value.IsProxy() && IsInitializedAssociation(fieldName))
125-
return InitializeOrGetAssociation((INHibernateProxy) value, fieldName);
137+
return InitializeOrGetAssociation(target, (INHibernateProxy) value, fieldName);
126138

127139
return value;
128140
}
@@ -144,7 +156,7 @@ public object Intercept(object target, string fieldName, object value, bool sett
144156
if (value.IsProxy() && IsUninitializedAssociation(fieldName))
145157
{
146158
var nhproxy = value as INHibernateProxy;
147-
return InitializeOrGetAssociation(nhproxy, fieldName);
159+
return InitializeOrGetAssociation(target, nhproxy, fieldName);
148160
}
149161
return InvokeImplementation;
150162
}
@@ -164,13 +176,19 @@ private bool IsUninitializedProperty(string fieldName)
164176
return uninitializedFields != null && uninitializedFields.Contains(fieldName);
165177
}
166178

167-
private object InitializeOrGetAssociation(INHibernateProxy value, string fieldName)
179+
private object InitializeOrGetAssociation(object target, INHibernateProxy value, string fieldName)
168180
{
169181
if(value.HibernateLazyInitializer.IsUninitialized)
170182
{
171183
value.HibernateLazyInitializer.Initialize();
172184
value.HibernateLazyInitializer.Unwrap = true; // means that future Load/Get from the session will get the implementation
173185
loadedUnwrapProxyFieldNames.Add(fieldName);
186+
// Set the property value in order to be accessible when the session is closed
187+
var implValue = value.HibernateLazyInitializer.GetImplementation(session);
188+
var persister = session.Factory.GetEntityPersister(entityName);
189+
persister.SetPropertyValue(target, persister.GetUnwrapProxyPropertyIndex(fieldName), implValue);
190+
191+
return implValue;
174192
}
175193
return value.HibernateLazyInitializer.GetImplementation(session);
176194
}

src/NHibernate/Persister/Entity/IEntityPersister.cs

+22
Original file line numberDiff line numberDiff line change
@@ -714,5 +714,27 @@ internal static IFieldInterceptor ExtractFieldInterceptor(this IEntityPersister
714714
return FieldInterceptionHelper.ExtractFieldInterceptor(entity);
715715
#pragma warning restore CS0618
716716
}
717+
718+
/// <summary>
719+
/// Get the index of the property in terms of its position in the entity persister which is mapped as proxy="no-proxy"
720+
/// </summary>
721+
//6.0 TODO: Remove and replace usages with UnwrapProxyPropertiesMetadata.GetUnwrapProxyPropertyIndex instead
722+
internal static int GetUnwrapProxyPropertyIndex(this IEntityPersister persister, string propertyName)
723+
{
724+
if (persister is AbstractEntityPersister abstractEntityPersister)
725+
{
726+
return abstractEntityPersister.InstrumentationMetadata.UnwrapProxyPropertiesMetadata
727+
.GetUnwrapProxyPropertyIndex(propertyName);
728+
}
729+
730+
var index = Array.IndexOf(persister.PropertyNames, propertyName);
731+
if (index < 0)
732+
{
733+
throw new InvalidOperationException(
734+
$"Property {propertyName} is not mapped as proxy=\"no-proxy\" on entity {persister.EntityName}");
735+
}
736+
737+
return index;
738+
}
717739
}
718740
}

src/NHibernate/Proxy/FieldInterceptorObjectReference.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ private FieldInterceptorObjectReference(SerializationInfo info, StreamingContext
3232

3333
if (info.GetBoolean(HasAdditionalDataName))
3434
{
35-
_deserializedProxy = _proxyFactoryInfo.CreateProxyFactory().GetFieldInterceptionProxy(null);
35+
_deserializedProxy = _proxyFactoryInfo.CreateProxyFactory().GetFieldInterceptionProxy();
3636

3737
var additionalMembers = info.GetValue<MemberInfo[]>(AdditionalMemberName);
3838
if (additionalMembers == null)
@@ -62,7 +62,7 @@ private FieldInterceptorObjectReference(SerializationInfo info, StreamingContext
6262
{
6363
// Base type has a custom serialization, we need to call the proxy deserialization for deserializing
6464
// base type members too.
65-
var proxyType = _proxyFactoryInfo.CreateProxyFactory().GetFieldInterceptionProxy(null).GetType();
65+
var proxyType = _proxyFactoryInfo.CreateProxyFactory().GetFieldInterceptionProxy().GetType();
6666
var deserializationConstructor = proxyType.GetConstructor(
6767
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public,
6868
null,

src/NHibernate/Proxy/IProxyFactory.cs

+32
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Reflection;
34
using NHibernate.Engine;
@@ -49,6 +50,37 @@ void PostInstantiate(string entityName, System.Type persistentClass, ISet<System
4950
/// <exception cref="HibernateException">Indicates problems generating requested proxy.</exception>
5051
INHibernateProxy GetProxy(object id, ISessionImplementor session);
5152

53+
// Since 5.3
54+
[Obsolete("This ProxyFactoryExtensions.GetFieldInterceptionProxy extension method instead.")]
5255
object GetFieldInterceptionProxy(object instanceToWrap);
5356
}
57+
58+
public static class ProxyFactoryExtensions
59+
{
60+
// 6.0 TODO: Remove
61+
internal static object GetFieldInterceptionProxy(this IProxyFactory proxyFactory, Func<object> instantiateFunc)
62+
{
63+
if (proxyFactory is StaticProxyFactory staticProxyFactory)
64+
{
65+
return staticProxyFactory.GetFieldInterceptionProxy();
66+
}
67+
68+
#pragma warning disable 618
69+
return proxyFactory.GetFieldInterceptionProxy(instantiateFunc?.Invoke());
70+
#pragma warning restore 618
71+
}
72+
73+
// 6.0 TODO: Move to IProxyFactory
74+
public static object GetFieldInterceptionProxy(this IProxyFactory proxyFactory)
75+
{
76+
if (proxyFactory is StaticProxyFactory staticProxyFactory)
77+
{
78+
return staticProxyFactory.GetFieldInterceptionProxy();
79+
}
80+
81+
#pragma warning disable 618
82+
return proxyFactory.GetFieldInterceptionProxy(null);
83+
#pragma warning restore 618
84+
}
85+
}
5486
}

src/NHibernate/Proxy/StaticProxyFactory.cs

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ private Func<ILazyInitializer, NHibernateProxyFactoryInfo, INHibernateProxy> Cre
4343
}
4444

4545
public override object GetFieldInterceptionProxy(object instanceToWrap)
46+
{
47+
return GetFieldInterceptionProxy();
48+
}
49+
50+
public object GetFieldInterceptionProxy()
4651
{
4752
var cacheEntry = new ProxyCacheEntry(PersistentClass, System.Type.EmptyTypes);
4853
var proxyActivator = FieldInterceptorCache.GetOrAdd(cacheEntry, CreateFieldInterceptionProxyActivator);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using NHibernate.Bytecode;
3+
using NHibernate.Mapping;
4+
using NHibernate.Proxy;
5+
6+
namespace NHibernate.Tuple.Entity
7+
{
8+
/// <summary> Defines a POCO-based instantiator for use from the <see cref="PocoEntityTuplizer"/>.</summary>
9+
[Serializable]
10+
public class PocoEntityInstantiator : PocoInstantiator
11+
{
12+
private readonly EntityMetamodel _entityMetamodel;
13+
private readonly System.Type _proxyInterface;
14+
private readonly bool _enhancedForLazyLoading;
15+
private readonly IProxyFactory _proxyFactory;
16+
17+
public PocoEntityInstantiator(
18+
EntityMetamodel entityMetamodel,
19+
PersistentClass persistentClass,
20+
IInstantiationOptimizer optimizer,
21+
IProxyFactory proxyFactory)
22+
: base(
23+
persistentClass.MappedClass,
24+
optimizer,
25+
persistentClass.HasEmbeddedIdentifier)
26+
{
27+
_entityMetamodel = entityMetamodel;
28+
_proxyInterface = persistentClass.ProxyInterface;
29+
_enhancedForLazyLoading = entityMetamodel.BytecodeEnhancementMetadata.EnhancedForLazyLoading;
30+
_proxyFactory = proxyFactory;
31+
}
32+
33+
protected override object CreateInstance()
34+
{
35+
if (!_enhancedForLazyLoading)
36+
{
37+
return base.CreateInstance();
38+
}
39+
40+
var entity = _proxyFactory.GetFieldInterceptionProxy(CreateInstance);
41+
_entityMetamodel.BytecodeEnhancementMetadata.InjectInterceptor(entity, true, null);
42+
return entity;
43+
}
44+
45+
public override bool IsInstance(object obj)
46+
{
47+
return base.IsInstance(obj) ||
48+
// this one needed only for guessEntityMode()
49+
(_proxyInterface != null && _proxyInterface.IsInstanceOfType(obj));
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)