Skip to content

Commit 6cb478f

Browse files
maca88fredericDelaporte
authored andcommitted
Port Hibernate's lazy attribute fetch groups (#1949)
1 parent fff0d08 commit 6cb478f

File tree

66 files changed

+1368
-173
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1368
-173
lines changed

doc/reference/modules/basic_mapping.xml

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,6 +1694,7 @@
16941694
<area id="property15" coords="16 55" />
16951695
<area id="property16" coords="17 55" />
16961696
<area id="property17" coords="18 55" />
1697+
<area id="property18" coords="19 55" />
16971698
</areaspec>
16981699
<programlisting><![CDATA[<property
16991700
name="propertyName"
@@ -1706,6 +1707,7 @@
17061707
optimistic-lock="true|false"
17071708
generated="never|insert|always"
17081709
lazy="true|false"
1710+
lazy-group="groupName"
17091711
not-null="true|false"
17101712
unique="true|false"
17111713
unique-key="uniqueKeyName"
@@ -1773,55 +1775,63 @@
17731775
<literal>lazy</literal> (optional - defaults to <literal>false</literal>):
17741776
specifies that this property is lazy. A lazy property is not loaded when
17751777
the object is initially loaded, unless the fetch mode has been overridden
1776-
in a specific query. Values for lazy properties are loaded when any lazy
1777-
property of the object is accessed. Having lazy properties causes instances
1778-
of the entity to be loaded as proxies. Theses proxies ignore the class
1779-
<literal>proxy</literal> setting and always derives from the persistent class,
1780-
requiring its members to be overridable.
1778+
in a specific query. Values for lazy properties are loaded per lazy-group.
1779+
</para>
1780+
<para>
1781+
Having lazy properties causes instances of the entity to be loaded as proxies.
1782+
Theses proxies ignore the class <literal>proxy</literal> setting and always
1783+
derives from the persistent class, requiring its members to be overridable.
1784+
</para>
1785+
</callout>
1786+
<callout arearefs="property11">
1787+
<para>
1788+
<literal>lazy-group</literal> (optional - defaults to <literal>DEFAULT</literal>):
1789+
if the property is lazy, its lazy-loading group. When a lazy property is accessed,
1790+
the other lazy properties of the lazy group are also loaded with it.
17811791
</para>
17821792
</callout>
1783-
<callout arearefs="property11">
1793+
<callout arearefs="property12">
17841794
<para>
17851795
<literal>not-null</literal> (optional - defaults to <literal>false</literal>):
17861796
sets the column nullability for DDL generation.
17871797
</para>
17881798
</callout>
1789-
<callout arearefs="property12">
1799+
<callout arearefs="property13">
17901800
<para>
17911801
<literal>unique</literal> (optional - defaults to <literal>false</literal>):
17921802
sets the column uniqueness for DDL generation. Use <literal>unique-key</literal>
17931803
instead if the value is unique only in combination with other properties.
17941804
</para>
17951805
</callout>
1796-
<callout arearefs="property13">
1806+
<callout arearefs="property14">
17971807
<para>
17981808
<literal>unique-key</literal> (optional):
17991809
a logical name for an unique index for DDL generation. The column will be included in
18001810
the index, along with other columns sharing the same <literal>unique-key</literal>
18011811
logical name. The actual index name depends on the dialect.
18021812
</para>
18031813
</callout>
1804-
<callout arearefs="property14">
1814+
<callout arearefs="property15">
18051815
<para>
18061816
<literal>index</literal> (optional):
18071817
a logical name for an index for DDL generation. The column will be included in
18081818
the index, along with other columns sharing the same <literal>index</literal> logical
18091819
name. The actual index name depends on the dialect.
18101820
</para>
18111821
</callout>
1812-
<callout arearefs="property15">
1822+
<callout arearefs="property16">
18131823
<para>
18141824
<literal>length</literal> (optional): if the type takes a length and does not
18151825
already specify it, its length.
18161826
</para>
18171827
</callout>
1818-
<callout arearefs="property16">
1828+
<callout arearefs="property17">
18191829
<para>
18201830
<literal>precision</literal> (optional): if the type takes a precision and does not
18211831
already specify it, its precision.
18221832
</para>
18231833
</callout>
1824-
<callout arearefs="property17">
1834+
<callout arearefs="property18">
18251835
<para>
18261836
<literal>scale</literal> (optional): if the type takes a scale and does not
18271837
already specify it, its scale.
@@ -2598,6 +2608,7 @@
25982608
<area id="component6" coords="7 60"/>
25992609
<area id="component7" coords="8 60"/>
26002610
<area id="component8" coords="9 60"/>
2611+
<area id="component9" coords="10 60"/>
26012612
</areaspec>
26022613
<programlisting><![CDATA[<component
26032614
name="propertyName"
@@ -2607,6 +2618,7 @@
26072618
access="field|property|nosetter|className"
26082619
optimistic-lock="true|false"
26092620
lazy="true|false"
2621+
lazy-group="groupName"
26102622
unique="true|false">
26112623
26122624
<property ... />
@@ -2660,6 +2672,14 @@
26602672
</para>
26612673
</callout>
26622674
<callout arearefs="component8">
2675+
<para>
2676+
<literal>lazy-group</literal> (optional - defaults to <literal>DEFAULT</literal>):
2677+
If the component is lazy, its lazy-loading group. When a lazy property is accessed
2678+
on an object, included when the property is a component, the other lazy properties
2679+
of the lazy group are also loaded with it.
2680+
</para>
2681+
</callout>
2682+
<callout arearefs="component9">
26632683
<para>
26642684
<literal>unique</literal> (optional - defaults to <literal>false</literal>): Specifies
26652685
that an unique constraint exists upon all mapped columns of the component.

src/NHibernate.DomainModel/Async/CustomPersister.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,8 @@ public async Task<object> LoadAsync(object id, object optionalObject, LockMode l
8585
if (obj != null)
8686
{
8787
clone = (Custom)obj.Clone();
88-
TwoPhaseLoad.AddUninitializedEntity(session.GenerateEntityKey(id, this), clone, this, LockMode.None, false,
89-
session);
90-
TwoPhaseLoad.PostHydrate(this, id, new String[] {obj.Name}, null, clone, LockMode.None, false, session);
88+
TwoPhaseLoad.AddUninitializedEntity(session.GenerateEntityKey(id, this), clone, this, LockMode.None, session);
89+
TwoPhaseLoad.PostHydrate(this, id, new String[] {obj.Name}, null, clone, LockMode.None, session);
9190
await (TwoPhaseLoad.InitializeEntityAsync(clone, false, session, new PreLoadEvent((IEventSource) session),
9291
new PostLoadEvent((IEventSource) session), cancellationToken));
9392
}

src/NHibernate.DomainModel/CustomPersister.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,9 +310,8 @@ public object Load(object id, object optionalObject, LockMode lockMode, ISession
310310
if (obj != null)
311311
{
312312
clone = (Custom)obj.Clone();
313-
TwoPhaseLoad.AddUninitializedEntity(session.GenerateEntityKey(id, this), clone, this, LockMode.None, false,
314-
session);
315-
TwoPhaseLoad.PostHydrate(this, id, new String[] {obj.Name}, null, clone, LockMode.None, false, session);
313+
TwoPhaseLoad.AddUninitializedEntity(session.GenerateEntityKey(id, this), clone, this, LockMode.None, session);
314+
TwoPhaseLoad.PostHydrate(this, id, new String[] {obj.Name}, null, clone, LockMode.None, session);
316315
TwoPhaseLoad.InitializeEntity(clone, false, session, new PreLoadEvent((IEventSource) session),
317316
new PostLoadEvent((IEventSource) session));
318317
}

src/NHibernate.Test/Async/CacheTest/SerializationFixture.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,7 @@ private CacheEntry CreateCacheEntry()
142142
{
143143
DisassembledState = GetAllKnownTypeValues(),
144144
Version = 55,
145-
Subclass = "Test",
146-
AreLazyPropertiesUnfetched = true
145+
Subclass = "Test"
147146
};
148147
}
149148

@@ -240,7 +239,6 @@ private void CheckCacheEntry(CacheEntry original, CacheEntry copy)
240239
Assert.That(copy.Version, Is.EqualTo(original.Version));
241240
Assert.That(copy.Version, Is.TypeOf(original.Version.GetType()));
242241
Assert.That(copy.Subclass, Is.EqualTo(original.Subclass));
243-
Assert.That(copy.AreLazyPropertiesUnfetched, Is.EqualTo(original.AreLazyPropertiesUnfetched));
244242
for (var i = 0; i < copy.DisassembledState.Length; i++)
245243
{
246244
Assert.That(copy.DisassembledState[i], Is.TypeOf(original.DisassembledState[i].GetType()));
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System.Collections.Generic;
12+
using System.Linq;
13+
using System.Text;
14+
using System.Threading.Tasks;
15+
using NHibernate.Cache;
16+
using NHibernate.Cfg;
17+
using NUnit.Framework;
18+
19+
namespace NHibernate.Test.LazyGroup
20+
{
21+
using System.Threading;
22+
[TestFixture]
23+
public class LazyGroupFixtureAsync : TestCase
24+
{
25+
protected override string MappingsAssembly => "NHibernate.Test";
26+
27+
protected override string[] Mappings => new[] { "LazyGroup.Mappings.hbm.xml" };
28+
29+
protected override void Configure(Configuration configuration)
30+
{
31+
base.Configure(configuration);
32+
configuration.Properties[Environment.CacheProvider] = typeof(HashtableCacheProvider).AssemblyQualifiedName;
33+
configuration.Properties[Environment.UseSecondLevelCache] = "true";
34+
configuration.Properties[Environment.GenerateStatistics] = "true";
35+
}
36+
37+
protected override void OnSetUp()
38+
{
39+
using (var s = OpenSession())
40+
using (var tx = s.BeginTransaction())
41+
{
42+
for (var i = 1; i <= 5; i++)
43+
{
44+
var person = GeneratePerson(i);
45+
s.Save(person);
46+
}
47+
48+
tx.Commit();
49+
}
50+
}
51+
52+
protected override void OnTearDown()
53+
{
54+
using (var s = OpenSession())
55+
using (var tx = s.BeginTransaction())
56+
{
57+
s.CreateQuery("delete from Person").ExecuteUpdate();
58+
tx.Commit();
59+
}
60+
}
61+
62+
[Test]
63+
public async Task TestGroupsAsync()
64+
{
65+
using (var s = OpenSession())
66+
{
67+
var person = await (s.GetAsync<Person>(1));
68+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Name"), Is.True);
69+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "NickName"), Is.False);
70+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Address"), Is.False);
71+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.False);
72+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Age"), Is.False);
73+
74+
var nickName = person.NickName;
75+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "NickName"), Is.True);
76+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Address"), Is.False);
77+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.False);
78+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Age"), Is.False);
79+
Assert.That(nickName, Is.EqualTo("NickName1"));
80+
81+
var address = person.Address;
82+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Address"), Is.True);
83+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.False);
84+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Age"), Is.False);
85+
Assert.That(address.City, Is.EqualTo("City1"));
86+
Assert.That(address.Street, Is.EqualTo("Street1"));
87+
Assert.That(address.PostCode, Is.EqualTo(1001));
88+
89+
var image = person.Image;
90+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.True);
91+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Age"), Is.False);
92+
Assert.That(person.Image, Has.Length.EqualTo(1));
93+
94+
var age = person.Age;
95+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Age"), Is.True);
96+
Assert.That(person.Age, Is.EqualTo(1));
97+
}
98+
}
99+
100+
[TestCase(true)]
101+
[TestCase(false)]
102+
public async Task TestUpdateAsync(bool fetchBeforeUpdate, CancellationToken cancellationToken = default(CancellationToken))
103+
{
104+
Sfi.Statistics.Clear();
105+
106+
using (var s = OpenSession())
107+
using (var tx = s.BeginTransaction())
108+
{
109+
var person = await (s.GetAsync<Person>(1, cancellationToken));
110+
if (fetchBeforeUpdate)
111+
{
112+
var nickName = person.NickName;
113+
}
114+
115+
person.NickName = "test";
116+
117+
await (tx.CommitAsync(cancellationToken));
118+
}
119+
120+
Assert.That(Sfi.Statistics.EntityUpdateCount, Is.EqualTo(1));
121+
122+
using (var s = OpenSession())
123+
using (var tx = s.BeginTransaction())
124+
{
125+
var person = await (s.GetAsync<Person>(1, cancellationToken));
126+
Assert.That(person.NickName, Is.EqualTo("test"));
127+
128+
person.NickName = "NickName1"; // reset name
129+
130+
await (tx.CommitAsync(cancellationToken));
131+
}
132+
}
133+
134+
[Test]
135+
public async Task TestCacheAsync()
136+
{
137+
var persister = Sfi.GetEntityPersister(typeof(Person).FullName);
138+
var cache = (HashtableCache) persister.Cache.Cache;
139+
await (cache.ClearAsync(CancellationToken.None));
140+
141+
using (var s = OpenSession())
142+
using (var tx = s.BeginTransaction())
143+
{
144+
var person = await (s.GetAsync<Person>(1));
145+
146+
var nickName = person.NickName;
147+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "NickName"), Is.True);
148+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Address"), Is.False);
149+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.False);
150+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Age"), Is.False);
151+
Assert.That(nickName, Is.EqualTo("NickName1"));
152+
153+
await (tx.CommitAsync());
154+
}
155+
156+
using (var s = OpenSession())
157+
using (var tx = s.BeginTransaction())
158+
{
159+
var person = await (s.GetAsync<Person>(1));
160+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "NickName"), Is.True);
161+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Address"), Is.False);
162+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.False);
163+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Age"), Is.False);
164+
Assert.That(person.NickName, Is.EqualTo("NickName1"));
165+
166+
await (tx.CommitAsync());
167+
}
168+
}
169+
170+
[Test]
171+
public async Task TestInitializeFromCacheAsync()
172+
{
173+
var persister = Sfi.GetEntityPersister(typeof(Person).FullName);
174+
var cache = (HashtableCache) persister.Cache.Cache;
175+
await (cache.ClearAsync(CancellationToken.None));
176+
Sfi.Statistics.Clear();
177+
178+
using (var s = OpenSession())
179+
using (var tx = s.BeginTransaction())
180+
{
181+
var person = await (s.GetAsync<Person>(1));
182+
183+
await (InitializeImageAsync());
184+
185+
Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2));
186+
187+
var image = person.Image; // Should be initialized from cache
188+
189+
Assert.That(Sfi.Statistics.PrepareStatementCount, Is.EqualTo(2));
190+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "NickName"), Is.False);
191+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Address"), Is.False);
192+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Image"), Is.True);
193+
Assert.That(NHibernateUtil.IsPropertyInitialized(person, "Age"), Is.False);
194+
Assert.That(image, Has.Length.EqualTo(1));
195+
196+
await (tx.CommitAsync());
197+
}
198+
}
199+
200+
private async Task InitializeImageAsync(CancellationToken cancellationToken = default(CancellationToken))
201+
{
202+
using (var s = OpenSession())
203+
using (var tx = s.BeginTransaction())
204+
{
205+
var person = await (s.GetAsync<Person>(1, cancellationToken));
206+
var image = person.Image;
207+
208+
await (tx.CommitAsync(cancellationToken));
209+
}
210+
}
211+
212+
private static Person GeneratePerson(int i)
213+
{
214+
return new Person
215+
{
216+
Id = i,
217+
Name = $"Person{i}",
218+
Address = new Address
219+
{
220+
City = $"City{i}",
221+
PostCode = 1000+i,
222+
Street = $"Street{i}"
223+
},
224+
Image = new byte[i],
225+
NickName = $"NickName{i}"
226+
};
227+
228+
}
229+
}
230+
}

0 commit comments

Comments
 (0)