Skip to content

Commit b1d9304

Browse files
committed
Add support for fetching an individual lazy property with Criteria
1 parent f824b0b commit b1d9304

File tree

19 files changed

+310
-50
lines changed

19 files changed

+310
-50
lines changed

src/NHibernate.Test/Async/Criteria/SelectModeTest/SelectModeTest.cs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,53 @@ public async Task SelectModeFetchLazyPropertiesAsync()
153153
Assert.That(NHibernateUtil.IsInitialized(root), Is.True);
154154
Assert.That(root.LazyProp, Is.Not.Null);
155155
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.True, "Lazy property must be fetched.");
156+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched.");
157+
158+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
159+
}
160+
}
161+
162+
[Test]
163+
public async Task SelectModeFetchKeepLazyPropertiesUninitializedAsync()
164+
{
165+
using (var sqlLog = new SqlLogSpy())
166+
using (var session = OpenSession())
167+
{
168+
var root = await (session.QueryOver<EntityComplex>()
169+
.Fetch(SelectMode.Fetch, ec => ec)
170+
.Where(ec => ec.LazyProp != null)
171+
.Take(1)
172+
.SingleOrDefaultAsync());
173+
174+
Assert.That(root, Is.Not.Null);
175+
Assert.That(NHibernateUtil.IsInitialized(root), Is.True);
176+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.False, "Property must be lazy.");
177+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.False, "Property must be lazy.");
178+
179+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
180+
}
181+
}
182+
183+
[Test]
184+
public async Task SelectModeFetchLazyPropertiesFetchGroupAsync()
185+
{
186+
using (var sqlLog = new SqlLogSpy())
187+
using (var session = OpenSession())
188+
{
189+
var root = await (session.QueryOver<EntityComplex>()
190+
.Fetch(SelectMode.FetchProperty, ec => ec.LazyProp, ec => ec.LazyProp2, ec => ec.SameTypeChild.LazyProp2)
191+
.Where(ec => ec.Id == _parentEntityComplexId)
192+
.SingleOrDefaultAsync());
193+
194+
Assert.That(root, Is.Not.Null);
195+
Assert.That(NHibernateUtil.IsInitialized(root), Is.True);
196+
Assert.That(root.LazyProp, Is.Not.Null);
197+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.True, "Lazy property must be fetched.");
198+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched.");
199+
Assert.That(NHibernateUtil.IsInitialized(root.SameTypeChild), Is.True, "Object must be initialized.");
200+
Assert.That(root.SameTypeChild, Is.Not.Null);
201+
Assert.That(NHibernateUtil.IsPropertyInitialized(root.SameTypeChild, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched.");
202+
Assert.That(NHibernateUtil.IsPropertyInitialized(root.SameTypeChild, nameof(root.LazyProp)), Is.False, "Property must be lazy.");
156203

157204
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
158205
}
@@ -558,7 +605,16 @@ protected override HbmMapping GetMappings()
558605

559606
rc.Property(x => x.Name);
560607

561-
rc.Property(ep => ep.LazyProp, m => m.Lazy(true));
608+
rc.Property(ep => ep.LazyProp, m =>
609+
{
610+
m.Lazy(true);
611+
m.FetchGroup("LazyGroup");
612+
});
613+
rc.Property(ep => ep.LazyProp2, m =>
614+
{
615+
m.Lazy(true);
616+
m.FetchGroup("LazyGroup2");
617+
});
562618

563619
rc.ManyToOne(
564620
ep => ep.Child1,
@@ -731,9 +787,12 @@ protected override void OnSetUp()
731787
Child1 = child1,
732788
Child2 = child2,
733789
LazyProp = "SomeBigValue",
790+
LazyProp2 = "SomeBigValue2",
734791
SameTypeChild = new EntityComplex()
735792
{
736-
Name = "ComplexEntityChild"
793+
Name = "ComplexEntityChild",
794+
LazyProp = "LazyProp1",
795+
LazyProp2 = "LazyProp2",
737796
},
738797
ChildrenList = new List<EntitySimpleChild> {child3, child1, child4 },
739798
ChildrenListEmpty = new List<EntityComplex> { },

src/NHibernate.Test/Criteria/SelectModeTest/Entities.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public class EntityComplex
1212
public virtual string Name { get; set; }
1313

1414
public virtual string LazyProp { get; set; }
15+
public virtual string LazyProp2 { get; set; }
1516

1617
public virtual EntitySimpleChild Child1 { get; set; }
1718
public virtual EntitySimpleChild Child2 { get; set; }

src/NHibernate.Test/Criteria/SelectModeTest/SelectModeTest.cs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,53 @@ public void SelectModeFetchLazyProperties()
142142
Assert.That(NHibernateUtil.IsInitialized(root), Is.True);
143143
Assert.That(root.LazyProp, Is.Not.Null);
144144
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.True, "Lazy property must be fetched.");
145+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched.");
146+
147+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
148+
}
149+
}
150+
151+
[Test]
152+
public void SelectModeFetchKeepLazyPropertiesUninitialized()
153+
{
154+
using (var sqlLog = new SqlLogSpy())
155+
using (var session = OpenSession())
156+
{
157+
var root = session.QueryOver<EntityComplex>()
158+
.Fetch(SelectMode.Fetch, ec => ec)
159+
.Where(ec => ec.LazyProp != null)
160+
.Take(1)
161+
.SingleOrDefault();
162+
163+
Assert.That(root, Is.Not.Null);
164+
Assert.That(NHibernateUtil.IsInitialized(root), Is.True);
165+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.False, "Property must be lazy.");
166+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.False, "Property must be lazy.");
167+
168+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
169+
}
170+
}
171+
172+
[Test]
173+
public void SelectModeFetchLazyPropertiesFetchGroup()
174+
{
175+
using (var sqlLog = new SqlLogSpy())
176+
using (var session = OpenSession())
177+
{
178+
var root = session.QueryOver<EntityComplex>()
179+
.Fetch(SelectMode.FetchProperty, ec => ec.LazyProp, ec => ec.LazyProp2, ec => ec.SameTypeChild.LazyProp2)
180+
.Where(ec => ec.Id == _parentEntityComplexId)
181+
.SingleOrDefault();
182+
183+
Assert.That(root, Is.Not.Null);
184+
Assert.That(NHibernateUtil.IsInitialized(root), Is.True);
185+
Assert.That(root.LazyProp, Is.Not.Null);
186+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.True, "Lazy property must be fetched.");
187+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched.");
188+
Assert.That(NHibernateUtil.IsInitialized(root.SameTypeChild), Is.True, "Object must be initialized.");
189+
Assert.That(root.SameTypeChild, Is.Not.Null);
190+
Assert.That(NHibernateUtil.IsPropertyInitialized(root.SameTypeChild, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched.");
191+
Assert.That(NHibernateUtil.IsPropertyInitialized(root.SameTypeChild, nameof(root.LazyProp)), Is.False, "Property must be lazy.");
145192

146193
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
147194
}
@@ -589,7 +636,16 @@ protected override HbmMapping GetMappings()
589636

590637
rc.Property(x => x.Name);
591638

592-
rc.Property(ep => ep.LazyProp, m => m.Lazy(true));
639+
rc.Property(ep => ep.LazyProp, m =>
640+
{
641+
m.Lazy(true);
642+
m.FetchGroup("LazyGroup");
643+
});
644+
rc.Property(ep => ep.LazyProp2, m =>
645+
{
646+
m.Lazy(true);
647+
m.FetchGroup("LazyGroup2");
648+
});
593649

594650
rc.ManyToOne(
595651
ep => ep.Child1,
@@ -762,9 +818,12 @@ protected override void OnSetUp()
762818
Child1 = child1,
763819
Child2 = child2,
764820
LazyProp = "SomeBigValue",
821+
LazyProp2 = "SomeBigValue2",
765822
SameTypeChild = new EntityComplex()
766823
{
767-
Name = "ComplexEntityChild"
824+
Name = "ComplexEntityChild",
825+
LazyProp = "LazyProp1",
826+
LazyProp2 = "LazyProp2",
768827
},
769828
ChildrenList = new List<EntitySimpleChild> {child3, child1, child4 },
770829
ChildrenListEmpty = new List<EntityComplex> { },

src/NHibernate/Async/Persister/Collection/AbstractCollectionPersister.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ namespace NHibernate.Persister.Collection
4040
using System.Threading.Tasks;
4141
using System.Threading;
4242
public abstract partial class AbstractCollectionPersister : ICollectionMetadata, ISqlLoadableCollection,
43-
IPostInsertIdentityPersister, ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister
43+
IPostInsertIdentityPersister, ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister, ISupportLazyPropsJoinable
4444
{
4545

4646
public Task InitializeAsync(object key, ISessionImplementor session, CancellationToken cancellationToken)

src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ namespace NHibernate.Persister.Entity
4545
using System.Threading;
4646
public abstract partial class AbstractEntityPersister : IOuterJoinLoadable, IQueryable, IClassMetadata,
4747
IUniqueKeyLoadable, ISqlLoadable, ILazyPropertyInitializer, IPostInsertIdentityPersister, ILockable,
48-
ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister
48+
ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister, ISupportLazyPropsJoinable
4949
{
5050

5151
private partial class GeneratedIdentifierBinder : IBinder

src/NHibernate/Impl/CriteriaImpl.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public partial class CriteriaImpl : ICriteria, ISupportEntityJoinCriteria, ISupp
2222
private readonly List<OrderEntry> orderEntries = new List<OrderEntry>(10);
2323
private readonly Dictionary<string, SelectMode> selectModes = new Dictionary<string, SelectMode>();
2424
private readonly Dictionary<string, LockMode> lockModes = new Dictionary<string, LockMode>();
25+
private readonly Dictionary<string, HashSet<string>> _entityFetchLazyProperties = new Dictionary<string, HashSet<string>>();
26+
2527
private int maxResults = RowSelection.NoValue;
2628
private int firstResult;
2729
private int timeout = RowSelection.NoValue;
@@ -167,6 +169,16 @@ public SelectMode GetSelectMode(string path)
167169
return result;
168170
}
169171

172+
public HashSet<string> GetEntityFetchLazyProperties(string path)
173+
{
174+
if (_entityFetchLazyProperties.TryGetValue(path, out var result))
175+
{
176+
return result;
177+
}
178+
return null;
179+
}
180+
181+
170182
public IResultTransformer ResultTransformer
171183
{
172184
get { return resultTransformer; }
@@ -366,6 +378,19 @@ public ICriteria Fetch(SelectMode selectMode, string associationPath, string ali
366378
return this;
367379
}
368380

381+
if (selectMode == SelectMode.FetchProperty)
382+
{
383+
StringHelper.ParsePathAndPropertyName(associationPath, out associationPath, out var propertyName);
384+
if (_entityFetchLazyProperties.TryGetValue(associationPath, out var propertyNames))
385+
{
386+
propertyNames.Add(propertyName);
387+
}
388+
else
389+
{
390+
_entityFetchLazyProperties[associationPath] = new HashSet<string> {propertyName};
391+
}
392+
}
393+
369394
selectModes[associationPath] = selectMode;
370395
return this;
371396
}

src/NHibernate/Loader/Criteria/CriteriaJoinWalker.cs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,29 +99,37 @@ protected override void WalkEntityTree(IOuterJoinLoadable persister, string alia
9999

100100
protected override OuterJoinableAssociation CreateRootAssociation()
101101
{
102-
var selectMode = GetSelectMode(string.Empty);
102+
var path = string.Empty;
103+
var selectMode = GetSelectMode(path);
103104
if (selectMode == SelectMode.JoinOnly || selectMode == SelectMode.Skip)
104105
{
105106
throw new NotSupportedException($"SelectMode {selectMode} for root entity is not supported. Use {nameof(SelectMode)}.{nameof(SelectMode.ChildFetch)} instead.");
106107
}
107108

108-
return new OuterJoinableAssociation(
109-
Persister.EntityType,
110-
null,
111-
null,
112-
Alias,
113-
JoinType.LeftOuterJoin,
114-
null,
115-
Factory,
116-
CollectionHelper.EmptyDictionary<string, IFilter>(),
117-
selectMode);
109+
return InitAssociation(
110+
new OuterJoinableAssociation(
111+
Persister.EntityType,
112+
null,
113+
null,
114+
Alias,
115+
JoinType.LeftOuterJoin,
116+
null,
117+
Factory,
118+
CollectionHelper.EmptyDictionary<string, IFilter>(),
119+
selectMode),
120+
path);
118121
}
119122

120123
protected override SelectMode GetSelectMode(string path)
121124
{
122125
return translator.RootCriteria.GetSelectMode(path);
123126
}
124127

128+
protected override ISet<string> GetEntityFetchLazyProperties(string path)
129+
{
130+
return translator.RootCriteria.GetEntityFetchLazyProperties(path);
131+
}
132+
125133
private void WalkCompositeComponentIdTree(IOuterJoinLoadable persister, string alias, string path)
126134
{
127135
IType type = persister.IdentifierType;
@@ -197,6 +205,7 @@ protected override JoinType GetJoinType(IAssociationType type, FetchMode config,
197205

198206
case SelectMode.Fetch:
199207
case SelectMode.FetchLazyProperties:
208+
case SelectMode.FetchProperty:
200209
case SelectMode.ChildFetch:
201210
case SelectMode.JoinOnly:
202211
IsDuplicateAssociation(lhsTable, lhsColumns, type); //deliberately ignore return value!

src/NHibernate/Loader/Criteria/CriteriaLoader.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public CriteriaLoader(IOuterJoinLoadable persister, ISessionFactoryImplementor f
5454
includeInResultRow = walker.IncludeInResultRow;
5555
resultRowLength = ArrayHelper.CountTrue(IncludeInResultRow);
5656
childFetchEntities = walker.ChildFetchEntities;
57+
EntityFetchLazyProperties = walker.EntityFetchLazyProperties;
5758
// fill caching objects only if there is a projection
5859
if (translator.HasProjection)
5960
{
@@ -95,6 +96,8 @@ protected override bool IsChildFetchEntity(int i)
9596
return childFetchEntities?[i] == true;
9697
}
9798

99+
protected override ISet<string>[] EntityFetchLazyProperties { get; }
100+
98101
public IList List(ISessionImplementor session)
99102
{
100103
return List(session, translator.GetQueryParameters(), querySpaces);

src/NHibernate/Loader/Hql/QueryLoader.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ protected override bool[] EntityEagerPropertyFetches
119119
get { return _entityEagerPropertyFetches; }
120120
}
121121

122-
protected override HashSet<string>[] EntityFetchLazyProperties
122+
protected override ISet<string>[] EntityFetchLazyProperties
123123
{
124124
get { return _entityFetchLazyProperties; }
125125
}

0 commit comments

Comments
 (0)