Skip to content

Commit b9dc310

Browse files
bahusoidhazzik
andauthored
Add support for fetching an individual lazy property with Criteria (#2097)
Co-authored-by: Alexander Zaytsev <alexzaytsev2019@gmail.com>
1 parent 97d2184 commit b9dc310

21 files changed

+362
-95
lines changed

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

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,53 @@ public async Task SelectModeFetchLazyPropertiesAsync()
174174
Assert.That(NHibernateUtil.IsInitialized(root), Is.True);
175175
Assert.That(root.LazyProp, Is.Not.Null);
176176
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.True, "Lazy property must be fetched.");
177+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched.");
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 SelectModeFetchKeepLazyPropertiesUninitializedAsync()
185+
{
186+
using (var sqlLog = new SqlLogSpy())
187+
using (var session = OpenSession())
188+
{
189+
var root = await (session.QueryOver<EntityComplex>()
190+
.Fetch(SelectMode.Fetch, ec => ec)
191+
.Where(ec => ec.LazyProp != null)
192+
.Take(1)
193+
.SingleOrDefaultAsync());
194+
195+
Assert.That(root, Is.Not.Null);
196+
Assert.That(NHibernateUtil.IsInitialized(root), Is.True);
197+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.False, "Property must be lazy.");
198+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.False, "Property must be lazy.");
199+
200+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
201+
}
202+
}
203+
204+
[Test]
205+
public async Task SelectModeFetchLazyPropertiesFetchGroupAsync()
206+
{
207+
using (var sqlLog = new SqlLogSpy())
208+
using (var session = OpenSession())
209+
{
210+
var root = await (session.QueryOver<EntityComplex>()
211+
.Fetch(SelectMode.FetchLazyPropertyGroup, ec => ec.LazyProp, ec => ec.LazyProp2, ec => ec.SameTypeChild.LazyProp2)
212+
.Where(ec => ec.Id == _parentEntityComplexId)
213+
.SingleOrDefaultAsync());
214+
215+
Assert.That(root, Is.Not.Null);
216+
Assert.That(NHibernateUtil.IsInitialized(root), Is.True);
217+
Assert.That(root.LazyProp, Is.Not.Null);
218+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.True, "Lazy property must be fetched.");
219+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched.");
220+
Assert.That(NHibernateUtil.IsInitialized(root.SameTypeChild), Is.True, "Object must be initialized.");
221+
Assert.That(root.SameTypeChild, Is.Not.Null);
222+
Assert.That(NHibernateUtil.IsPropertyInitialized(root.SameTypeChild, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched.");
223+
Assert.That(NHibernateUtil.IsPropertyInitialized(root.SameTypeChild, nameof(root.LazyProp)), Is.False, "Property must be lazy.");
177224

178225
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
179226
}
@@ -579,7 +626,16 @@ protected override HbmMapping GetMappings()
579626

580627
rc.Property(x => x.Name);
581628

582-
rc.Property(ep => ep.LazyProp, m => m.Lazy(true));
629+
rc.Property(ep => ep.LazyProp, m =>
630+
{
631+
m.Lazy(true);
632+
m.FetchGroup("LazyGroup");
633+
});
634+
rc.Property(ep => ep.LazyProp2, m =>
635+
{
636+
m.Lazy(true);
637+
m.FetchGroup("LazyGroup2");
638+
});
583639

584640
rc.ManyToOne(
585641
ep => ep.Child1,
@@ -751,9 +807,12 @@ protected override void OnSetUp()
751807
Child1 = child1,
752808
Child2 = child2,
753809
LazyProp = "SomeBigValue",
810+
LazyProp2 = "SomeBigValue2",
754811
SameTypeChild = new EntityComplex()
755812
{
756-
Name = "ComplexEntityChild"
813+
Name = "ComplexEntityChild",
814+
LazyProp = "LazyProp1",
815+
LazyProp2 = "LazyProp2",
757816
},
758817
ChildrenList = new List<EntitySimpleChild> {child3, child1, child4 },
759818
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
@@ -163,6 +163,53 @@ public void SelectModeFetchLazyProperties()
163163
Assert.That(NHibernateUtil.IsInitialized(root), Is.True);
164164
Assert.That(root.LazyProp, Is.Not.Null);
165165
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.True, "Lazy property must be fetched.");
166+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched.");
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 SelectModeFetchKeepLazyPropertiesUninitialized()
174+
{
175+
using (var sqlLog = new SqlLogSpy())
176+
using (var session = OpenSession())
177+
{
178+
var root = session.QueryOver<EntityComplex>()
179+
.Fetch(SelectMode.Fetch, ec => ec)
180+
.Where(ec => ec.LazyProp != null)
181+
.Take(1)
182+
.SingleOrDefault();
183+
184+
Assert.That(root, Is.Not.Null);
185+
Assert.That(NHibernateUtil.IsInitialized(root), Is.True);
186+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.False, "Property must be lazy.");
187+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.False, "Property must be lazy.");
188+
189+
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
190+
}
191+
}
192+
193+
[Test]
194+
public void SelectModeFetchLazyPropertiesFetchGroup()
195+
{
196+
using (var sqlLog = new SqlLogSpy())
197+
using (var session = OpenSession())
198+
{
199+
var root = session.QueryOver<EntityComplex>()
200+
.Fetch(SelectMode.FetchLazyPropertyGroup, ec => ec.LazyProp, ec => ec.LazyProp2, ec => ec.SameTypeChild.LazyProp2)
201+
.Where(ec => ec.Id == _parentEntityComplexId)
202+
.SingleOrDefault();
203+
204+
Assert.That(root, Is.Not.Null);
205+
Assert.That(NHibernateUtil.IsInitialized(root), Is.True);
206+
Assert.That(root.LazyProp, Is.Not.Null);
207+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp)), Is.True, "Lazy property must be fetched.");
208+
Assert.That(NHibernateUtil.IsPropertyInitialized(root, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched.");
209+
Assert.That(NHibernateUtil.IsInitialized(root.SameTypeChild), Is.True, "Object must be initialized.");
210+
Assert.That(root.SameTypeChild, Is.Not.Null);
211+
Assert.That(NHibernateUtil.IsPropertyInitialized(root.SameTypeChild, nameof(root.LazyProp2)), Is.True, "Lazy property must be fetched.");
212+
Assert.That(NHibernateUtil.IsPropertyInitialized(root.SameTypeChild, nameof(root.LazyProp)), Is.False, "Property must be lazy.");
166213

167214
Assert.That(sqlLog.Appender.GetEvents().Length, Is.EqualTo(1), "Only one SQL select is expected");
168215
}
@@ -610,7 +657,16 @@ protected override HbmMapping GetMappings()
610657

611658
rc.Property(x => x.Name);
612659

613-
rc.Property(ep => ep.LazyProp, m => m.Lazy(true));
660+
rc.Property(ep => ep.LazyProp, m =>
661+
{
662+
m.Lazy(true);
663+
m.FetchGroup("LazyGroup");
664+
});
665+
rc.Property(ep => ep.LazyProp2, m =>
666+
{
667+
m.Lazy(true);
668+
m.FetchGroup("LazyGroup2");
669+
});
614670

615671
rc.ManyToOne(
616672
ep => ep.Child1,
@@ -782,9 +838,12 @@ protected override void OnSetUp()
782838
Child1 = child1,
783839
Child2 = child2,
784840
LazyProp = "SomeBigValue",
841+
LazyProp2 = "SomeBigValue2",
785842
SameTypeChild = new EntityComplex()
786843
{
787-
Name = "ComplexEntityChild"
844+
Name = "ComplexEntityChild",
845+
LazyProp = "LazyProp1",
846+
LazyProp2 = "LazyProp2",
788847
},
789848
ChildrenList = new List<EntitySimpleChild> {child3, child1, child4 },
790849
ChildrenListEmpty = new List<EntityComplex> { },

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,8 @@ namespace NHibernate.Persister.Collection
3939
{
4040
using System.Threading.Tasks;
4141
using System.Threading;
42-
4342
public abstract partial class AbstractCollectionPersister : ICollectionMetadata, ISqlLoadableCollection,
44-
IPostInsertIdentityPersister, ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister
43+
IPostInsertIdentityPersister, ISupportSelectModeJoinable, ICompositeKeyPostInsertIdentityPersister, ISupportLazyPropsJoinable
4544
{
4645

4746
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: 24 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,15 @@ 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+
170181
public IResultTransformer ResultTransformer
171182
{
172183
get { return resultTransformer; }
@@ -366,6 +377,19 @@ public ICriteria Fetch(SelectMode selectMode, string associationPath, string ali
366377
return this;
367378
}
368379

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

src/NHibernate/Loader/AbstractEntityJoinWalker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ private void InitStatementString(OuterJoinableAssociation rootAssociation, SqlSt
114114

115115
Suffixes = BasicLoader.GenerateSuffixes(joins + 1);
116116
var suffix = Suffixes[joins];
117-
selectClause = new SqlString(GetSelectFragment(rootAssociation, suffix, null) + SelectString(associations));
117+
selectClause = new SqlString(rootAssociation.GetSelectFragment(suffix, null, null) + SelectString(associations));
118118
}
119119

120120
JoinFragment ojf = MergeOuterJoins(associations);

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.FetchLazyPropertyGroup:
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
@@ -120,7 +120,7 @@ protected override bool[] EntityEagerPropertyFetches
120120
get { return _entityEagerPropertyFetches; }
121121
}
122122

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

0 commit comments

Comments
 (0)