Skip to content

Commit 88df525

Browse files
committed
adding support for ghost properties
SVN: trunk@4925
1 parent 1b6a478 commit 88df525

19 files changed

+264
-52
lines changed

src/NHibernate.ByteCode.Castle/LazyFieldInterceptor.cs

+9-7
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,22 @@ public void Intercept(IInvocation invocation)
1818
{
1919
if (ReflectHelper.IsPropertyGet(invocation.Method))
2020
{
21-
var result = FieldInterceptor.Intercept(invocation.InvocationTarget, ReflectHelper.GetPropertyName(invocation.Method));
22-
if (result == AbstractFieldInterceptor.InvokeImplementation)
23-
{
24-
invocation.Proceed();
25-
}
26-
else
21+
invocation.Proceed(); // get the existing value
22+
23+
var result = FieldInterceptor.Intercept(
24+
invocation.InvocationTarget,
25+
ReflectHelper.GetPropertyName(invocation.Method),
26+
invocation.ReturnValue);
27+
28+
if (result != AbstractFieldInterceptor.InvokeImplementation)
2729
{
2830
invocation.ReturnValue = result;
2931
}
3032
}
3133
else if (ReflectHelper.IsPropertySet(invocation.Method))
3234
{
3335
FieldInterceptor.MarkDirty();
34-
FieldInterceptor.Intercept(invocation.InvocationTarget, ReflectHelper.GetPropertyName(invocation.Method));
36+
FieldInterceptor.Intercept(invocation.InvocationTarget, ReflectHelper.GetPropertyName(invocation.Method), null);
3537
invocation.Proceed();
3638
}
3739
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System.Collections;
2+
using NHibernate.ByteCode.Castle;
3+
using NHibernate.Cfg;
4+
using NUnit.Framework;
5+
6+
namespace NHibernate.Test.GhostProperty
7+
{
8+
[TestFixture]
9+
public class GhostPropertyFixture : TestCase
10+
{
11+
protected override string MappingsAssembly
12+
{
13+
get { return "NHibernate.Test"; }
14+
}
15+
16+
protected override IList Mappings
17+
{
18+
get { return new[] { "GhostProperty.Mappings.hbm.xml" }; }
19+
}
20+
21+
protected override void Configure(NHibernate.Cfg.Configuration configuration)
22+
{
23+
configuration.SetProperty(Environment.ProxyFactoryFactoryClass,
24+
typeof(ProxyFactoryFactory).AssemblyQualifiedName);
25+
}
26+
27+
protected override void OnSetUp()
28+
{
29+
using (var s = OpenSession())
30+
using (var tx = s.BeginTransaction())
31+
{
32+
var wireTransfer = new WireTransfer
33+
{
34+
Id = 1
35+
};
36+
s.Persist(wireTransfer);
37+
s.Persist(new Order
38+
{
39+
Id = 1,
40+
Payment = wireTransfer
41+
});
42+
tx.Commit();
43+
}
44+
45+
}
46+
47+
protected override void OnTearDown()
48+
{
49+
using (var s = OpenSession())
50+
using (var tx = s.BeginTransaction())
51+
{
52+
s.Delete("from Order");
53+
s.Delete("from Payment");
54+
tx.Commit();
55+
}
56+
}
57+
58+
[Test]
59+
public void CanGetActualValueFromLazyManyToOne()
60+
{
61+
using (ISession s = OpenSession())
62+
{
63+
var order = s.Get<Order>(1);
64+
65+
Assert.IsTrue(order.Payment is WireTransfer);
66+
}
67+
}
68+
69+
[Test]
70+
public void GhostPropertyMaintainIdentityMap()
71+
{
72+
using (ISession s = OpenSession())
73+
{
74+
var order = s.Get<Order>(1);
75+
76+
Assert.AreSame(order.Payment, s.Load<Payment>(1));
77+
}
78+
}
79+
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3+
assembly="NHibernate.Test"
4+
namespace="NHibernate.Test.GhostProperty">
5+
6+
<class name="Order" table="Orders">
7+
<id name="Id">
8+
<generator class="assigned" />
9+
</id>
10+
<many-to-one name="Payment" force-load-on-property-access="true"/>
11+
</class>
12+
13+
14+
<class name="Payment" abstract="true">
15+
<id name="Id">
16+
<generator class="assigned" />
17+
</id>
18+
<discriminator column="Type" type="System.String"/>
19+
<subclass name="WireTransfer" discriminator-value="WT">
20+
21+
</subclass>
22+
<subclass name="CreditCard" discriminator-value="CC">
23+
24+
</subclass>
25+
26+
</class>
27+
28+
</hibernate-mapping>
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
namespace NHibernate.Test.GhostProperty
2+
{
3+
public class Order
4+
{
5+
public virtual int Id { get; set; }
6+
public virtual Payment Payment { get; set; }
7+
}
8+
9+
public abstract class Payment
10+
{
11+
public virtual int Id { get; set; }
12+
}
13+
14+
public class WireTransfer : Payment{}
15+
public class CreditCard : Payment { }
16+
}

src/NHibernate.Test/LazyProperty/LazyPropertyFixture.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public void PropertyLoadedNotInitialized()
8585
[Test]
8686
public void ShouldGenerateErrorForNonAutoPropLazyProp()
8787
{
88-
Assert.IsTrue(log.Contains("Lazy property NHibernate.Test.LazyProperty.Book.ALotOfText is not an auto property, which may result in uninitialized property access"));
88+
Assert.IsTrue(log.Contains("Lazy or ghost property NHibernate.Test.LazyProperty.Book.ALotOfText is not an auto property, which may result in uninitialized property access"));
8989
}
9090

9191
[Test]

src/NHibernate.Test/NHibernate.Test.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,8 @@
341341
<Compile Include="GenericTest\SetGeneric\A.cs" />
342342
<Compile Include="GenericTest\SetGeneric\B.cs" />
343343
<Compile Include="GenericTest\SetGeneric\SetGenericFixture.cs" />
344+
<Compile Include="GhostProperty\Order.cs" />
345+
<Compile Include="GhostProperty\GhostPropertyFixture.cs" />
344346
<Compile Include="HQL\Animal.cs" />
345347
<Compile Include="HQL\Ast\Address.cs" />
346348
<Compile Include="HQL\Ast\Animal.cs" />
@@ -2113,6 +2115,7 @@
21132115
<EmbeddedResource Include="CfgTest\Loquacious\EntityToCache.hbm.xml" />
21142116
<EmbeddedResource Include="DriverTest\SqlServerCeEntity.hbm.xml" />
21152117
<Content Include="DynamicEntity\package.html" />
2118+
<EmbeddedResource Include="GhostProperty\Mappings.hbm.xml" />
21162119
<EmbeddedResource Include="NHSpecificTest\NH2065\Mappings.hbm.xml" />
21172120
<EmbeddedResource Include="NHSpecificTest\NH2009\Mappings.hbm.xml" />
21182121
<EmbeddedResource Include="NHSpecificTest\NH1989\Mappings.hbm.xml" />

src/NHibernate/Cfg/MappingSchema/Hbm.generated.cs

+10-2
Original file line numberDiff line numberDiff line change
@@ -672,11 +672,19 @@ public partial class HbmManyToOne {
672672
/// <remarks/>
673673
[System.Xml.Serialization.XmlAttributeAttribute("not-null")]
674674
public bool notnull;
675-
675+
676676
/// <remarks/>
677677
[System.Xml.Serialization.XmlIgnoreAttribute()]
678678
public bool notnullSpecified;
679-
679+
680+
/// <remarks/>
681+
[System.Xml.Serialization.XmlAttributeAttribute("force-load-on-property-access")]
682+
public bool forceloadonpropertyaccess;
683+
684+
/// <remarks/>
685+
[System.Xml.Serialization.XmlAttributeAttribute()]
686+
public bool forceloadonpropertyaccessSpecified;
687+
680688
/// <remarks/>
681689
[System.Xml.Serialization.XmlAttributeAttribute()]
682690
[System.ComponentModel.DefaultValueAttribute(false)]

src/NHibernate/Cfg/MappingSchema/HbmManyToOne.cs

+5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ public bool OptimisticLock
2828
get { return optimisticlock; }
2929
}
3030

31+
public bool ForceLoadOnPropertyAccess
32+
{
33+
get { return forceloadonpropertyaccess; }
34+
}
35+
3136
#endregion
3237

3338
#region Overrides of AbstractDecoratable

src/NHibernate/Cfg/XmlHbmBinding/PropertiesBinder.cs

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ public void Bind(IEnumerable<IEntityPropertyMapping> properties, Table table, ID
107107
var value = new ManyToOne(table);
108108
BindManyToOne(manyToOneMapping, value, propertyName, true);
109109
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
110+
property.IsGhostProperty = manyToOneMapping.ForceLoadOnPropertyAccess;
110111
BindManyToOneProperty(manyToOneMapping, property);
111112
}
112113
else if ((componentMapping = entityPropertyMapping as HbmComponent) != null)

src/NHibernate/Event/Default/AbstractSaveEventListener.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ private void MarkInterceptorDirty(object entity, IEntityPersister persister, IEv
292292
{
293293
if (FieldInterceptionHelper.IsInstrumented(entity))
294294
{
295-
IFieldInterceptor interceptor = FieldInterceptionHelper.InjectFieldInterceptor(entity, persister.EntityName, null, source);
295+
IFieldInterceptor interceptor = FieldInterceptionHelper.InjectFieldInterceptor(entity, persister.EntityName, null, null, source);
296296
interceptor.MarkDirty();
297297
}
298298
}

src/NHibernate/Intercept/AbstractFieldInterceptor.cs

+34-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using Iesi.Collections.Generic;
33
using NHibernate.Engine;
4+
using NHibernate.Proxy;
45

56
namespace NHibernate.Intercept
67
{
@@ -12,16 +13,18 @@ public abstract class AbstractFieldInterceptor : IFieldInterceptor
1213
[NonSerialized]
1314
private ISessionImplementor session;
1415
private ISet<string> uninitializedFields;
16+
private ISet<string> uninitializedGhostFieldNames;
1517
private readonly string entityName;
1618

1719
[NonSerialized]
1820
private bool initializing;
1921
private bool isDirty;
2022

21-
protected internal AbstractFieldInterceptor(ISessionImplementor session, ISet<string> uninitializedFields, string entityName)
23+
protected internal AbstractFieldInterceptor(ISessionImplementor session, ISet<string> uninitializedFields, ISet<string> uninitializedGhostFieldNames, string entityName)
2224
{
2325
this.session = session;
2426
this.uninitializedFields = uninitializedFields;
27+
this.uninitializedGhostFieldNames = uninitializedGhostFieldNames;
2528
this.entityName = entityName;
2629
}
2730

@@ -74,11 +77,9 @@ public bool Initializing
7477
get { return initializing; }
7578
}
7679

77-
public object Intercept(object target, string fieldName)
80+
public object Intercept(object target, string fieldName, object value)
7881
{
79-
if (initializing ||
80-
uninitializedFields == null ||
81-
!uninitializedFields.Contains(fieldName))
82+
if (initializing)
8283
return InvokeImplementation;
8384

8485
if (session == null)
@@ -90,11 +91,38 @@ public object Intercept(object target, string fieldName)
9091
throw new LazyInitializationException("session is not connected");
9192
}
9293

94+
if (uninitializedFields != null && uninitializedFields.Contains(fieldName))
95+
{
96+
return InitializeField(fieldName, target);
97+
}
98+
if (value is INHibernateProxy && uninitializedGhostFieldNames != null && uninitializedGhostFieldNames.Contains(fieldName))
99+
{
100+
return InitializeOrGetAssociation((INHibernateProxy)value);
101+
}
102+
return InvokeImplementation;
103+
}
104+
105+
private object InitializeOrGetAssociation(INHibernateProxy value)
106+
{
107+
if(value.HibernateLazyInitializer.IsUninitialized)
108+
{
109+
value.HibernateLazyInitializer.Initialize();
110+
var association = value.HibernateLazyInitializer.GetImplementation(session);
111+
var narrowedProxy = session.PersistenceContext.ProxyFor(association);
112+
// we set the narrowed impl here to be able to get it back in the future
113+
value.HibernateLazyInitializer.SetImplementation(narrowedProxy);
114+
}
115+
return value.HibernateLazyInitializer.GetImplementation(session);
116+
}
117+
118+
private object InitializeField(string fieldName, object target)
119+
{
93120
object result;
94121
initializing = true;
95122
try
96123
{
97-
result = ((ILazyPropertyInitializer)session.Factory.GetEntityPersister(entityName)).InitializeLazyProperty(fieldName, target, session);
124+
var lazyPropertyInitializer = ((ILazyPropertyInitializer) session.Factory.GetEntityPersister(entityName));
125+
result = lazyPropertyInitializer.InitializeLazyProperty(fieldName, target, session);
98126
}
99127
finally
100128
{

src/NHibernate/Intercept/DefaultFieldInterceptor.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ namespace NHibernate.Intercept
55
{
66
public class DefaultFieldInterceptor : AbstractFieldInterceptor
77
{
8-
public DefaultFieldInterceptor(ISessionImplementor session, ISet<string> uninitializedFields, string entityName)
9-
: base(session, uninitializedFields, entityName)
8+
public DefaultFieldInterceptor(ISessionImplementor session, ISet<string> uninitializedFields, ISet<string> uninitializedGhostFieldNames, string entityName)
9+
: base(session, uninitializedFields, uninitializedGhostFieldNames, entityName)
1010
{
1111
}
1212
}

src/NHibernate/Intercept/FieldInterceptionHelper.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,15 @@ public static IFieldInterceptor ExtractFieldInterceptor(object entity)
3232
return fieldInterceptorAccessor == null ? null : fieldInterceptorAccessor.FieldInterceptor;
3333
}
3434

35-
public static IFieldInterceptor InjectFieldInterceptor(object entity, string entityName, ISet<string> uninitializedFieldNames, ISessionImplementor session)
35+
public static IFieldInterceptor InjectFieldInterceptor(object entity, string entityName,
36+
ISet<string> uninitializedFieldNames,
37+
ISet<string> uninitializedGhostFieldNames,
38+
ISessionImplementor session)
3639
{
3740
var fieldInterceptorAccessor = entity as IFieldInterceptorAccessor;
3841
if (fieldInterceptorAccessor != null)
3942
{
40-
var fieldInterceptorImpl = new DefaultFieldInterceptor(session, uninitializedFieldNames, entityName);
43+
var fieldInterceptorImpl = new DefaultFieldInterceptor(session, uninitializedFieldNames, uninitializedGhostFieldNames, entityName);
4144
fieldInterceptorAccessor.FieldInterceptor = fieldInterceptorImpl;
4245
return fieldInterceptorImpl;
4346
}

src/NHibernate/Intercept/IFieldInterceptor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,6 @@ public interface IFieldInterceptor
2828
void ClearDirty();
2929

3030
/// <summary> Intercept field set/get </summary>
31-
object Intercept(object target, string fieldName);
31+
object Intercept(object target, string fieldName, object value);
3232
}
3333
}

0 commit comments

Comments
 (0)