Skip to content

Support fetching individual lazy properties for Criteria EntityProjection #2347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 31 additions & 1 deletion src/NHibernate.Test/Async/Criteria/EntityProjectionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,17 @@ protected override HbmMapping GetMappings()

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

rc.Property(ep => ep.LazyProp, m => m.Lazy(true));
rc.Property(ep => ep.LazyProp, m =>
{
m.Lazy(true);
m.FetchGroup("LazyProp1");
});

rc.Property(ep => ep.LazyProp2, m =>
{
m.Lazy(true);
m.FetchGroup("LazyProp2");
});

rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id"));
rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id"));
Expand Down Expand Up @@ -340,6 +350,26 @@ public async Task EntityProjectionWithLazyPropertiesFetchedAsync()
Assert.That(entityRoot, Is.Not.Null);
Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized");
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized");
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp2)), Is.True, "Lazy property must be initialized");
}
}

[Test]
public async Task EntityProjectionWithLazyPropertiesSinglePropertyFetchAsync()
{
using (var session = OpenSession())
{
EntityComplex entityRoot;
entityRoot = await (session
.QueryOver<EntityComplex>()
.Where(ec => ec.LazyProp != null)
.Select(Projections.RootEntity().SetFetchLazyPropertyGroups(nameof(entityRoot.LazyProp)))
.Take(1).SingleOrDefaultAsync());

Assert.That(entityRoot, Is.Not.Null);
Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized");
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized");
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp2)), Is.False, "Property must be lazy");
}
}

Expand Down
1 change: 1 addition & 0 deletions src/NHibernate.Test/Criteria/EntityProjectionsEntities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public class EntityComplex
public virtual string Name { get; set; }

public virtual string LazyProp { get; set; }
public virtual string LazyProp2 { get; set; }

public virtual EntitySimpleChild Child1 { get; set; }
public virtual EntitySimpleChild Child2 { get; set; }
Expand Down
32 changes: 31 additions & 1 deletion src/NHibernate.Test/Criteria/EntityProjectionsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,17 @@ protected override HbmMapping GetMappings()

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

rc.Property(ep => ep.LazyProp, m => m.Lazy(true));
rc.Property(ep => ep.LazyProp, m =>
{
m.Lazy(true);
m.FetchGroup("LazyProp1");
});

rc.Property(ep => ep.LazyProp2, m =>
{
m.Lazy(true);
m.FetchGroup("LazyProp2");
});

rc.ManyToOne(ep => ep.Child1, m => m.Column("Child1Id"));
rc.ManyToOne(ep => ep.Child2, m => m.Column("Child2Id"));
Expand Down Expand Up @@ -329,6 +339,26 @@ public void EntityProjectionWithLazyPropertiesFetched()
Assert.That(entityRoot, Is.Not.Null);
Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized");
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized");
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp2)), Is.True, "Lazy property must be initialized");
}
}

[Test]
public void EntityProjectionWithLazyPropertiesSinglePropertyFetch()
{
using (var session = OpenSession())
{
EntityComplex entityRoot;
entityRoot = session
.QueryOver<EntityComplex>()
.Where(ec => ec.LazyProp != null)
.Select(Projections.RootEntity().SetFetchLazyPropertyGroups(nameof(entityRoot.LazyProp)))
.Take(1).SingleOrDefault();

Assert.That(entityRoot, Is.Not.Null);
Assert.That(NHibernateUtil.IsInitialized(entityRoot), Is.True, "Object must be initialized");
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp)), Is.True, "Lazy property must be initialized");
Assert.That(NHibernateUtil.IsPropertyInitialized(entityRoot, nameof(entityRoot.LazyProp2)), Is.False, "Property must be lazy");
}
}

Expand Down
32 changes: 29 additions & 3 deletions src/NHibernate/Criterion/EntityProjection.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using NHibernate.Engine;
using NHibernate.Loader;
using NHibernate.Loader.Criteria;
using NHibernate.Persister.Entity;
using NHibernate.SqlCommand;
using NHibernate.Type;
using IQueryable = NHibernate.Persister.Entity.IQueryable;
Expand Down Expand Up @@ -38,10 +40,16 @@ public EntityProjection(System.Type entityType, string entityAlias)
}

/// <summary>
/// Fetch lazy properties
/// Fetch all lazy properties
/// </summary>
public bool FetchLazyProperties { get; set; }

/// <summary>
/// Fetch individual lazy properties or property groups
/// Note: To fetch single property it must be mapped with unique fetch group (lazy-group)
/// </summary>
public ICollection<string> FetchLazyPropertyGroups { get; set; }

/// <summary>
/// Lazy load entity
/// </summary>
Expand All @@ -63,14 +71,25 @@ public EntityProjection SetLazy(bool lazy = true)
}

/// <summary>
/// Fetch lazy properties
/// Fetch all lazy properties
/// </summary>
public EntityProjection SetFetchLazyProperties(bool fetchLazyProperties = true)
{
FetchLazyProperties = fetchLazyProperties;
return this;
}

/// <summary>
/// Fetch individual lazy properties or property groups
/// Provide lazy property name and it will be fetched along with properties that belong to the same fetch group (lazy-group)
/// Note: To fetch single property it must be mapped with unique fetch group (lazy-group)
/// </summary>
public EntityProjection SetFetchLazyPropertyGroups(params string[] lazyPropertyGroups)
{
FetchLazyPropertyGroups = lazyPropertyGroups;
return this;
}

#endregion Configuration methods

#region IProjection implementation
Expand Down Expand Up @@ -115,7 +134,14 @@ SqlString IProjection.ToSqlString(ICriteria criteria, int position, ICriteriaQue
? identifierSelectFragment
: string.Concat(
identifierSelectFragment,
Persister.PropertySelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyProperties)));
GetPropertySelectFragment()));
}

private string GetPropertySelectFragment()
{
return FetchLazyProperties
? Persister.PropertySelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyProperties)
: Persister.PropertySelectFragment(TableAlias, ColumnAliasSuffix, FetchLazyPropertyGroups);
}

SqlString IProjection.ToGroupSqlString(ICriteria criteria, ICriteriaQuery criteriaQuery)
Expand Down
8 changes: 7 additions & 1 deletion src/NHibernate/Loader/AbstractEntityJoinWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using System.Collections.Generic;
using NHibernate.Criterion;
using NHibernate.Engine;
using NHibernate.Loader.Criteria;
using NHibernate.Persister.Entity;
using NHibernate.SqlCommand;
using NHibernate.Type;
Expand Down Expand Up @@ -62,6 +61,7 @@ protected void InitProjection(SqlString projectionString, SqlString whereString,
var associations = new OuterJoinableAssociation[countEntities];
var eagerProps = new bool[countEntities];
var suffixes = new string[countEntities];
var fetchLazyProperties = new ISet<string>[countEntities];
for (var i = 0; i < countEntities; i++)
{
var e = entityProjections[i];
Expand All @@ -70,13 +70,19 @@ protected void InitProjection(SqlString projectionString, SqlString whereString,
{
eagerProps[i] = true;
}

if (e.FetchLazyPropertyGroups?.Count > 0)
{
fetchLazyProperties[i] = new HashSet<string>(e.FetchLazyPropertyGroups);
}
suffixes[i] = e.ColumnAliasSuffix;
}

InitPersisters(associations, lockMode);

Suffixes = suffixes;
EagerPropertyFetches = eagerProps;
EntityFetchLazyProperties = fetchLazyProperties;
}
else
{
Expand Down
3 changes: 2 additions & 1 deletion src/NHibernate/Loader/OuterJoinableAssociation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ internal string GetSelectFragment(string entitySuffix, string collectionSuffix,
ShouldFetchCollectionPersister(),
new EntityLoadInfo(entitySuffix)
{
LazyProperties = EntityFetchLazyProperties
LazyProperties = EntityFetchLazyProperties,
IncludeLazyProps = SelectMode == SelectMode.FetchLazyProperties,
});
case SelectMode.ChildFetch:
return ReflectHelper.CastOrThrow<ISupportSelectModeJoinable>(Joinable, "child fetch select mode")
Expand Down
3 changes: 2 additions & 1 deletion src/NHibernate/Persister/Entity/IQueryable.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using NHibernate.Util;

namespace NHibernate.Persister.Entity
Expand All @@ -16,7 +17,7 @@ internal static class AbstractEntityPersisterExtensions
/// Given a query alias and an identifying suffix, render the property select fragment.
/// </summary>
//6.0 TODO: Merge into IQueryable
public static string PropertySelectFragment(this IQueryable query, string alias, string suffix, string[] fetchProperties)
public static string PropertySelectFragment(this IQueryable query, string alias, string suffix, ICollection<string> fetchProperties)
{
return ReflectHelper.CastOrThrow<AbstractEntityPersister>(query, "individual lazy property fetches")
.PropertySelectFragment(alias, suffix, fetchProperties);
Expand Down
5 changes: 3 additions & 2 deletions src/NHibernate/SelectMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,13 @@ public enum SelectMode
JoinOnly,

/// <summary>
/// Skips fetching for eagerly mapped association (no-op for lazy association).
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It won't skip lazy="false" association from loading - so better say it explicitly which case is handled here

/// Skips fetching for fetch="join" association (no-op for lazy association).
/// </summary>
Skip,

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