Skip to content

Commit fffb8d2

Browse files
authored
Support fetching individual lazy properties for Criteria EntityProjection (#2347)
1 parent 500a8b8 commit fffb8d2

File tree

8 files changed

+106
-10
lines changed

8 files changed

+106
-10
lines changed

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,17 @@ protected override HbmMapping GetMappings()
4747

4848
rc.Property(x => x.Name);
4949

50-
rc.Property(ep => ep.LazyProp, m => m.Lazy(true));
50+
rc.Property(ep => ep.LazyProp, m =>
51+
{
52+
m.Lazy(true);
53+
m.FetchGroup("LazyProp1");
54+
});
55+
56+
rc.Property(ep => ep.LazyProp2, m =>
57+
{
58+
m.Lazy(true);
59+
m.FetchGroup("LazyProp2");
60+
});
5161

5262
rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id"));
5363
rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id"));
@@ -340,6 +350,26 @@ public async Task EntityProjectionWithLazyPropertiesFetchedAsync()
340350
Assert.That(entityRoot, Is.Not.Null);
341351
Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized");
342352
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized");
353+
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp2)), Is.True, "Lazy property must be initialized");
354+
}
355+
}
356+
357+
[Test]
358+
public async Task EntityProjectionWithLazyPropertiesSinglePropertyFetchAsync()
359+
{
360+
using (var session = OpenSession())
361+
{
362+
EntityComplex entityRoot;
363+
entityRoot = await (session
364+
.QueryOver<EntityComplex>()
365+
.Where(ec => ec.LazyProp != null)
366+
.Select(Projections.RootEntity().SetFetchLazyPropertyGroups(nameof(entityRoot.LazyProp)))
367+
.Take(1).SingleOrDefaultAsync());
368+
369+
Assert.That(entityRoot, Is.Not.Null);
370+
Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized");
371+
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized");
372+
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp2)), Is.False, "Property must be lazy");
343373
}
344374
}
345375

src/NHibernate.Test/Criteria/EntityProjectionsEntities.cs

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

2626
public virtual string LazyProp { get; set; }
27+
public virtual string LazyProp2 { get; set; }
2728

2829
public virtual EntitySimpleChild Child1 { get; set; }
2930
public virtual EntitySimpleChild Child2 { get; set; }

src/NHibernate.Test/Criteria/EntityProjectionsTest.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,17 @@ protected override HbmMapping GetMappings()
3636

3737
rc.Property(x => x.Name);
3838

39-
rc.Property(ep => ep.LazyProp, m => m.Lazy(true));
39+
rc.Property(ep => ep.LazyProp, m =>
40+
{
41+
m.Lazy(true);
42+
m.FetchGroup("LazyProp1");
43+
});
44+
45+
rc.Property(ep => ep.LazyProp2, m =>
46+
{
47+
m.Lazy(true);
48+
m.FetchGroup("LazyProp2");
49+
});
4050

4151
rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id"));
4252
rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id"));
@@ -329,6 +339,26 @@ public void EntityProjectionWithLazyPropertiesFetched()
329339
Assert.That(entityRoot, Is.Not.Null);
330340
Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized");
331341
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized");
342+
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp2)), Is.True, "Lazy property must be initialized");
343+
}
344+
}
345+
346+
[Test]
347+
public void EntityProjectionWithLazyPropertiesSinglePropertyFetch()
348+
{
349+
using (var session = OpenSession())
350+
{
351+
EntityComplex entityRoot;
352+
entityRoot = session
353+
.QueryOver<EntityComplex>()
354+
.Where(ec => ec.LazyProp != null)
355+
.Select(Projections.RootEntity().SetFetchLazyPropertyGroups(nameof(entityRoot.LazyProp)))
356+
.Take(1).SingleOrDefault();
357+
358+
Assert.That(entityRoot, Is.Not.Null);
359+
Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized");
360+
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized");
361+
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp2)), Is.False, "Property must be lazy");
332362
}
333363
}
334364

src/NHibernate/Criterion/EntityProjection.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
2+
using System.Collections.Generic;
23
using NHibernate.Engine;
34
using NHibernate.Loader;
45
using NHibernate.Loader.Criteria;
6+
using NHibernate.Persister.Entity;
57
using NHibernate.SqlCommand;
68
using NHibernate.Type;
79
using IQueryable = NHibernate.Persister.Entity.IQueryable;
@@ -38,10 +40,16 @@ public EntityProjection(System.Type entityType, string entityAlias)
3840
}
3941

4042
/// <summary>
41-
/// Fetch lazy properties
43+
/// Fetch all lazy properties
4244
/// </summary>
4345
public bool FetchLazyProperties { get; set; }
4446

47+
/// <summary>
48+
/// Fetch individual lazy properties or property groups
49+
/// Note: To fetch single property it must be mapped with unique fetch group (lazy-group)
50+
/// </summary>
51+
public ICollection<string> FetchLazyPropertyGroups { get; set; }
52+
4553
/// <summary>
4654
/// Lazy load entity
4755
/// </summary>
@@ -63,14 +71,25 @@ public EntityProjection SetLazy(bool lazy = true)
6371
}
6472

6573
/// <summary>
66-
/// Fetch lazy properties
74+
/// Fetch all lazy properties
6775
/// </summary>
6876
public EntityProjection SetFetchLazyProperties(bool fetchLazyProperties = true)
6977
{
7078
FetchLazyProperties = fetchLazyProperties;
7179
return this;
7280
}
7381

82+
/// <summary>
83+
/// Fetch individual lazy properties or property groups
84+
/// Provide lazy property name and it will be fetched along with properties that belong to the same fetch group (lazy-group)
85+
/// Note: To fetch single property it must be mapped with unique fetch group (lazy-group)
86+
/// </summary>
87+
public EntityProjection SetFetchLazyPropertyGroups(params string[] lazyPropertyGroups)
88+
{
89+
FetchLazyPropertyGroups = lazyPropertyGroups;
90+
return this;
91+
}
92+
7493
#endregion Configuration methods
7594

7695
#region IProjection implementation
@@ -115,7 +134,14 @@ SqlString IProjection.ToSqlString(ICriteria criteria, int position, ICriteriaQue
115134
? identifierSelectFragment
116135
: string.Concat(
117136
identifierSelectFragment,
118-
Persister.PropertySelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyProperties)));
137+
GetPropertySelectFragment()));
138+
}
139+
140+
private string GetPropertySelectFragment()
141+
{
142+
return FetchLazyProperties
143+
? Persister.PropertySelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyProperties)
144+
: Persister.PropertySelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyPropertyGroups);
119145
}
120146

121147
SqlString IProjection.ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery)

src/NHibernate/Loader/AbstractEntityJoinWalker.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
using System.Collections.Generic;
33
using NHibernate.Criterion;
44
using NHibernate.Engine;
5-
using NHibernate.Loader.Criteria;
65
using NHibernate.Persister.Entity;
76
using NHibernate.SqlCommand;
87
using NHibernate.Type;
@@ -62,6 +61,7 @@ protected void InitProjection(SqlString projectionString, SqlString whereString,
6261
var associations = new OuterJoinableAssociation[countEntities];
6362
var eagerProps = new bool[countEntities];
6463
var suffixes = new string[countEntities];
64+
var fetchLazyProperties = new ISet<string>[countEntities];
6565
for (var i = 0; i < countEntities; i++)
6666
{
6767
var e = entityProjections[i];
@@ -70,13 +70,19 @@ protected void InitProjection(SqlString projectionString, SqlString whereString,
7070
{
7171
eagerProps[i] = true;
7272
}
73+
74+
if (e.FetchLazyPropertyGroups?.Count > 0)
75+
{
76+
fetchLazyProperties[i] = new HashSet<string>(e.FetchLazyPropertyGroups);
77+
}
7378
suffixes[i] = e.ColumnAliasSuffix;
7479
}
7580

7681
InitPersisters(associations, lockMode);
7782

7883
Suffixes = suffixes;
7984
EagerPropertyFetches = eagerProps;
85+
EntityFetchLazyProperties = fetchLazyProperties;
8086
}
8187
else
8288
{

src/NHibernate/Loader/OuterJoinableAssociation.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,8 @@ internal string GetSelectFragment(string entitySuffix, string collectionSuffix,
236236
ShouldFetchCollectionPersister(),
237237
new EntityLoadInfo(entitySuffix)
238238
{
239-
LazyProperties = EntityFetchLazyProperties
239+
LazyProperties = EntityFetchLazyProperties,
240+
IncludeLazyProps = SelectMode == SelectMode.FetchLazyProperties,
240241
});
241242
case SelectMode.ChildFetch:
242243
return ReflectHelper.CastOrThrow<ISupportSelectModeJoinable>(Joinable, "child fetch select mode")

src/NHibernate/Persister/Entity/IQueryable.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using NHibernate.Util;
34

45
namespace NHibernate.Persister.Entity
@@ -16,7 +17,7 @@ internal static class AbstractEntityPersisterExtensions
1617
/// Given a query alias and an identifying suffix, render the property select fragment.
1718
/// </summary>
1819
//6.0 TODO: Merge into IQueryable
19-
public static string PropertySelectFragment(this IQueryable query, string alias, string suffix, string[] fetchProperties)
20+
public static string PropertySelectFragment(this IQueryable query, string alias, string suffix, ICollection<string> fetchProperties)
2021
{
2122
return ReflectHelper.CastOrThrow<AbstractEntityPersister>(query, "individual lazy property fetches")
2223
.PropertySelectFragment(alias, suffix, fetchProperties);

src/NHibernate/SelectMode.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@ public enum SelectMode
3333
JoinOnly,
3434

3535
/// <summary>
36-
/// Skips fetching for eagerly mapped association (no-op for lazy association).
36+
/// Skips fetching for fetch="join" association (no-op for lazy association).
3737
/// </summary>
3838
Skip,
3939

4040
/// <summary>
41-
/// Fetch lazy property group
41+
/// Fetch lazy property group.
42+
/// Provide path to lazy property and it will be fetched along with properties that belong to the same fetch group (lazy-group)
4243
/// Note: To fetch single property it must be mapped with unique fetch group (lazy-group)
4344
/// </summary>
4445
FetchLazyPropertyGroup,

0 commit comments

Comments
 (0)