Skip to content

Commit b14ce72

Browse files
committed
Reuse Loader logic for materializing entities from data reader
1 parent 289bda3 commit b14ce72

File tree

5 files changed

+192
-182
lines changed

5 files changed

+192
-182
lines changed

src/NHibernate/Async/Criterion/EntityProjectionType.cs

Lines changed: 12 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@
99

1010

1111
using System;
12+
using System.Collections.Generic;
1213
using System.Data.Common;
1314
using NHibernate.Engine;
14-
using NHibernate.Event;
15-
using NHibernate.Persister.Entity;
1615
using NHibernate.Type;
1716

1817
namespace NHibernate.Criterion
@@ -50,56 +49,24 @@ public override async Task<object> NullSafeGetAsync(DbDataReader rs, string name
5049
private async Task<object> GetInitializedEntityFromProjectionAsync(DbDataReader rs, ISessionImplementor session, object identifier, CancellationToken cancellationToken)
5150
{
5251
cancellationToken.ThrowIfCancellationRequested();
53-
var entity = await (CreateInitializedEntityAsync(
52+
var hydratedObjects = new List<object>();
53+
54+
var entity = await (Loader.Loader.GetOrCreateEntityFromDataReaderAsync(
5455
rs,
56+
null,
57+
null,
58+
hydratedObjects,
5559
session,
60+
session.GenerateEntityKey(identifier, _projection.Persister),
5661
_projection.Persister,
57-
identifier,
58-
_projection.PropertyColumnAliases,
5962
LockMode.None,
63+
_projection.EntityAliases,
64+
false,
6065
_projection.FetchLazyProperties,
61-
_projection.IsReadOnly, cancellationToken)).ConfigureAwait(false);
62-
63-
return entity;
64-
}
66+
null, cancellationToken)).ConfigureAwait(false);
6567

66-
private static async Task<object> CreateInitializedEntityAsync(DbDataReader rs, ISessionImplementor session, IQueryable persister, object identifier, string[][] propertyAliases, LockMode lockMode, bool fetchLazyProperties, bool readOnly, CancellationToken cancellationToken)
67-
{
68-
cancellationToken.ThrowIfCancellationRequested();
69-
var eventSource = session as IEventSource;
70-
PostLoadEvent postLoadEvent = null;
71-
PreLoadEvent preLoadEvent = null;
72-
object entity;
73-
if (eventSource != null)
74-
{
75-
preLoadEvent = new PreLoadEvent(eventSource);
76-
postLoadEvent = new PostLoadEvent(eventSource);
77-
entity = eventSource.Instantiate(persister, identifier);
78-
}
79-
else
80-
{
81-
entity = session.Instantiate(persister.EntityName, identifier);
82-
}
83-
84-
TwoPhaseLoad.AddUninitializedEntity(
85-
session.GenerateEntityKey(identifier, persister),
86-
entity,
87-
persister,
88-
lockMode,
89-
!fetchLazyProperties,
90-
session);
91-
92-
var hydrated = await (persister.HydrateAsync(
93-
rs,
94-
identifier,
95-
entity,
96-
persister,
97-
propertyAliases,
98-
fetchLazyProperties,
99-
session, cancellationToken)).ConfigureAwait(false);
68+
await (Loader.Loader.InitializeEntitiesAndCollectionsAsync(hydratedObjects, rs, session, _projection.IsReadOnly, null, cancellationToken)).ConfigureAwait(false);
10069

101-
TwoPhaseLoad.PostHydrate(persister, identifier, hydrated, null, entity, lockMode, !fetchLazyProperties, session);
102-
await (TwoPhaseLoad.InitializeEntityAsync(entity, readOnly, session, preLoadEvent, postLoadEvent, cancellationToken)).ConfigureAwait(false);
10370
return entity;
10471
}
10572
}

src/NHibernate/Async/Loader/Loader.cs

Lines changed: 76 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,18 @@ private async Task<IList> DoQueryAsync(ISessionImplementor session, QueryParamet
320320
}
321321
}
322322

323-
internal async Task InitializeEntitiesAndCollectionsAsync(IList hydratedObjects, object resultSetId, ISessionImplementor session, bool readOnly, CancellationToken cancellationToken)
323+
internal Task InitializeEntitiesAndCollectionsAsync(IList hydratedObjects, DbDataReader resultSetId, ISessionImplementor session, bool readOnly, CancellationToken cancellationToken)
324+
{
325+
if (cancellationToken.IsCancellationRequested)
326+
{
327+
return Task.FromCanceled<object>(cancellationToken);
328+
}
329+
return InitializeEntitiesAndCollectionsAsync(hydratedObjects, resultSetId, session, readOnly, CollectionPersisters, cancellationToken);
330+
}
331+
332+
internal static async Task InitializeEntitiesAndCollectionsAsync(IList hydratedObjects, DbDataReader resultSetId, ISessionImplementor session, bool readOnly, ICollectionPersister[] collectionPersisters, CancellationToken cancellationToken)
324333
{
325334
cancellationToken.ThrowIfCancellationRequested();
326-
ICollectionPersister[] collectionPersisters = CollectionPersisters;
327335
if (collectionPersisters != null)
328336
{
329337
for (int i = 0; i < collectionPersisters.Length; i++)
@@ -543,7 +551,7 @@ private async Task<EntityKey> GetKeyFromResultSetAsync(int i, IEntityPersister p
543551
/// if the version numbers are different.
544552
/// </summary>
545553
/// <exception cref="StaleObjectStateException"></exception>
546-
private async Task CheckVersionAsync(int i, IEntityPersister persister, object id, object entity, DbDataReader rs, ISessionImplementor session, CancellationToken cancellationToken)
554+
private static async Task CheckVersionAsync(IEntityPersister persister, object id, object entity, DbDataReader rs, ISessionImplementor session, IEntityAliases entityAliases, CancellationToken cancellationToken)
547555
{
548556
cancellationToken.ThrowIfCancellationRequested();
549557
object version = session.PersistenceContext.GetEntry(entity).Version;
@@ -552,7 +560,7 @@ private async Task CheckVersionAsync(int i, IEntityPersister persister, object i
552560
if (version != null)
553561
{
554562
IVersionType versionType = persister.VersionType;
555-
object currentVersion = await (versionType.NullSafeGetAsync(rs, EntityAliases[i].SuffixedVersionAliases, session, null, cancellationToken)).ConfigureAwait(false);
563+
object currentVersion = await (versionType.NullSafeGetAsync(rs, entityAliases.SuffixedVersionAliases, session, null, cancellationToken)).ConfigureAwait(false);
556564
if (!versionType.IsEqual(version, currentVersion))
557565
{
558566
if (session.Factory.Statistics.IsStatisticsEnabled)
@@ -577,7 +585,6 @@ private async Task<object[]> GetRowAsync(DbDataReader rs, ILoadable[] persisters
577585
{
578586
cancellationToken.ThrowIfCancellationRequested();
579587
int cols = persisters.Length;
580-
IEntityAliases[] descriptors = EntityAliases;
581588

582589
if (Log.IsDebugEnabled())
583590
{
@@ -586,6 +593,7 @@ private async Task<object[]> GetRowAsync(DbDataReader rs, ILoadable[] persisters
586593

587594
object[] rowResults = new object[cols];
588595

596+
var upgradeLocks = UpgradeLocks();
589597
for (int i = 0; i < cols; i++)
590598
{
591599
object obj = null;
@@ -601,31 +609,62 @@ private async Task<object[]> GetRowAsync(DbDataReader rs, ILoadable[] persisters
601609
}
602610
else
603611
{
604-
//If the object is already loaded, return the loaded one
605-
obj = await (session.GetEntityUsingInterceptorAsync(key, cancellationToken)).ConfigureAwait(false);
606-
if (obj != null)
607-
{
608-
//its already loaded so dont need to hydrate it
609-
await (InstanceAlreadyLoadedAsync(rs, i, persisters[i], key, obj, lockModes[i], session, cancellationToken)).ConfigureAwait(false);
610-
}
611-
else
612-
{
613-
obj =
614-
await (InstanceNotYetLoadedAsync(rs, i, persisters[i], key, lockModes[i], descriptors[i].RowIdAlias, optionalObjectKey,
615-
optionalObject, hydratedObjects, session, cancellationToken)).ConfigureAwait(false);
616-
}
612+
obj = await (GetOrCreateEntityFromDataReaderAsync(
613+
rs,
614+
optionalObject,
615+
optionalObjectKey,
616+
hydratedObjects,
617+
session,
618+
key,
619+
persisters[i],
620+
lockModes[i],
621+
EntityAliases[i],
622+
upgradeLocks,
623+
IsEagerPropertyFetchEnabled(i),
624+
OwnerAssociationTypes?[i], cancellationToken)).ConfigureAwait(false);
617625
}
618626

619627
rowResults[i] = obj;
620628
}
621629
return rowResults;
622630
}
623631

632+
/// <summary>
633+
/// Returns either already loaded entity from session or creates new entity from data reader. Returned created entity is not fully initialized - it's added to <paramref name="hydratedObjects"/> collection. To fully initialize entity this collection needs to be supplied to <see cref="InitializeEntitiesAndCollectionsAsync(System.Collections.IList,System.Data.Common.DbDataReader,NHibernate.Engine.ISessionImplementor,bool,CancellationToken)"/>
634+
/// </summary>
635+
internal static async Task<object> GetOrCreateEntityFromDataReaderAsync(DbDataReader rs, object optionalObject, EntityKey optionalObjectKey, IList hydratedObjects, ISessionImplementor session, EntityKey key, ILoadable persister, LockMode lockMode, IEntityAliases entityAliases, bool upgradeLocks, bool eagerPropertyFetch, EntityType ownerAssociationType, CancellationToken cancellationToken)
636+
{
637+
cancellationToken.ThrowIfCancellationRequested();
638+
//If the object is already loaded, return the loaded one
639+
object obj = await (session.GetEntityUsingInterceptorAsync(key, cancellationToken)).ConfigureAwait(false);
640+
if (obj != null)
641+
{
642+
//its already loaded so dont need to hydrate it
643+
await (InstanceAlreadyLoadedAsync(rs, persister, key, obj, lockMode, session, entityAliases, upgradeLocks, cancellationToken)).ConfigureAwait(false);
644+
}
645+
else
646+
{
647+
obj =
648+
await (InstanceNotYetLoadedAsync(
649+
rs,
650+
persister,
651+
key,
652+
lockMode,
653+
optionalObjectKey,
654+
optionalObject,
655+
hydratedObjects,
656+
session,
657+
entityAliases,
658+
eagerPropertyFetch,
659+
ownerAssociationType, cancellationToken)).ConfigureAwait(false);
660+
}
661+
return obj;
662+
}
663+
624664
/// <summary>
625665
/// The entity instance is already in the session cache
626666
/// </summary>
627-
private async Task InstanceAlreadyLoadedAsync(DbDataReader rs, int i, IEntityPersister persister, EntityKey key, object obj,
628-
LockMode lockMode, ISessionImplementor session, CancellationToken cancellationToken)
667+
private static async Task InstanceAlreadyLoadedAsync(DbDataReader rs, IEntityPersister persister, EntityKey key, object obj, LockMode lockMode, ISessionImplementor session, IEntityAliases entityAliases, bool upgradeLocks, CancellationToken cancellationToken)
629668
{
630669
cancellationToken.ThrowIfCancellationRequested();
631670
if (!persister.IsInstance(obj))
@@ -634,7 +673,7 @@ private async Task InstanceAlreadyLoadedAsync(DbDataReader rs, int i, IEntityPer
634673
throw new WrongClassException(errorMsg, key.Identifier, persister.EntityName);
635674
}
636675

637-
if (LockMode.None != lockMode && UpgradeLocks())
676+
if (LockMode.None != lockMode && upgradeLocks)
638677
{
639678
EntityEntry entry = session.PersistenceContext.GetEntry(obj);
640679
bool isVersionCheckNeeded = persister.IsVersioned && entry.LockMode.LessThan(lockMode);
@@ -645,7 +684,7 @@ private async Task InstanceAlreadyLoadedAsync(DbDataReader rs, int i, IEntityPer
645684
if (isVersionCheckNeeded)
646685
{
647686
// we only check the version when _upgrading_ lock modes
648-
await (CheckVersionAsync(i, persister, key.Identifier, obj, rs, session, cancellationToken)).ConfigureAwait(false);
687+
await (CheckVersionAsync(persister, key.Identifier, obj, rs, session, entityAliases, cancellationToken)).ConfigureAwait(false);
649688
// we need to upgrade the lock mode to the mode requested
650689
entry.LockMode = lockMode;
651690
}
@@ -655,14 +694,12 @@ private async Task InstanceAlreadyLoadedAsync(DbDataReader rs, int i, IEntityPer
655694
/// <summary>
656695
/// The entity instance is not in the session cache
657696
/// </summary>
658-
private async Task<object> InstanceNotYetLoadedAsync(DbDataReader dr, int i, ILoadable persister, EntityKey key, LockMode lockMode,
659-
string rowIdAlias, EntityKey optionalObjectKey, object optionalObject,
660-
IList hydratedObjects, ISessionImplementor session, CancellationToken cancellationToken)
697+
private static async Task<object> InstanceNotYetLoadedAsync(DbDataReader dr, ILoadable persister, EntityKey key, LockMode lockMode, EntityKey optionalObjectKey, object optionalObject, IList hydratedObjects, ISessionImplementor session, IEntityAliases entityAliases, bool eagerPropertyFetch, EntityType ownerAssociationType, CancellationToken cancellationToken)
661698
{
662699
cancellationToken.ThrowIfCancellationRequested();
663700
object obj;
664701

665-
string instanceClass = await (GetInstanceClassAsync(dr, i, persister, key.Identifier, session, cancellationToken)).ConfigureAwait(false);
702+
string instanceClass = await (GetInstanceClassAsync(dr, persister, key.Identifier, session, entityAliases, cancellationToken)).ConfigureAwait(false);
666703

667704
if (optionalObjectKey != null && key.Equals(optionalObjectKey))
668705
{
@@ -680,7 +717,7 @@ private async Task<object> InstanceNotYetLoadedAsync(DbDataReader dr, int i, ILo
680717
// (but don't yet initialize the object itself)
681718
// note that we acquired LockMode.READ even if it was not requested
682719
LockMode acquiredLockMode = lockMode == LockMode.None ? LockMode.Read : lockMode;
683-
await (LoadFromResultSetAsync(dr, i, obj, instanceClass, key, rowIdAlias, acquiredLockMode, persister, session, cancellationToken)).ConfigureAwait(false);
720+
await (LoadFromResultSetAsync(dr, obj, instanceClass, key, acquiredLockMode, persister, session, eagerPropertyFetch, entityAliases, ownerAssociationType, cancellationToken)).ConfigureAwait(false);
684721

685722
// materialize associations (and initialize the object) later
686723
hydratedObjects.Add(obj);
@@ -693,41 +730,39 @@ private async Task<object> InstanceNotYetLoadedAsync(DbDataReader dr, int i, ILo
693730
/// an array of "hydrated" values (do not resolve associations yet),
694731
/// and pass the hydrated state to the session.
695732
/// </summary>
696-
private async Task LoadFromResultSetAsync(DbDataReader rs, int i, object obj, string instanceClass, EntityKey key,
697-
string rowIdAlias, LockMode lockMode, ILoadable rootPersister,
698-
ISessionImplementor session, CancellationToken cancellationToken)
733+
private static async Task LoadFromResultSetAsync(DbDataReader rs, object obj, string instanceClass, EntityKey key, LockMode lockMode, ILoadable rootPersister,
734+
ISessionImplementor session, bool eagerPropertyFetch,IEntityAliases entityAlias, EntityType ownerAssociationType, CancellationToken cancellationToken)
699735
{
700736
cancellationToken.ThrowIfCancellationRequested();
701737
object id = key.Identifier;
702738

703739
// Get the persister for the _subclass_
704-
ILoadable persister = (ILoadable)Factory.GetEntityPersister(instanceClass);
740+
ILoadable persister = rootPersister.HasSubclasses
741+
? (ILoadable) session.Factory.GetEntityPersister(instanceClass)
742+
: rootPersister;
705743

706744
if (Log.IsDebugEnabled())
707745
{
708746
Log.Debug("Initializing object from DataReader: {0}", MessageHelper.InfoString(persister, id));
709747
}
710748

711-
bool eagerPropertyFetch = IsEagerPropertyFetchEnabled(i);
712-
713749
// add temp entry so that the next step is circular-reference
714750
// safe - only needed because some types don't take proper
715751
// advantage of two-phase-load (esp. components)
716752
TwoPhaseLoad.AddUninitializedEntity(key, obj, persister, lockMode, !eagerPropertyFetch, session);
717753

718754
// This is not very nice (and quite slow):
719755
string[][] cols = persister == rootPersister
720-
? EntityAliases[i].SuffixedPropertyAliases
721-
: EntityAliases[i].GetSuffixedPropertyAliases(persister);
756+
? entityAlias.SuffixedPropertyAliases
757+
: entityAlias.GetSuffixedPropertyAliases(persister);
722758

723759
object[] values = await (persister.HydrateAsync(rs, id, obj, rootPersister, cols, eagerPropertyFetch, session, cancellationToken)).ConfigureAwait(false);
724760

725-
object rowId = persister.HasRowId ? rs[rowIdAlias] : null;
761+
object rowId = persister.HasRowId ? rs[entityAlias.RowIdAlias] : null;
726762

727-
IAssociationType[] ownerAssociationTypes = OwnerAssociationTypes;
728-
if (ownerAssociationTypes != null && ownerAssociationTypes[i] != null)
763+
if (ownerAssociationType != null)
729764
{
730-
string ukName = ownerAssociationTypes[i].RHSUniqueKeyPropertyName;
765+
string ukName = ownerAssociationType.RHSUniqueKeyPropertyName;
731766
if (ukName != null)
732767
{
733768
int index = ((IUniqueKeyLoadable)persister).GetPropertyIndex(ukName);
@@ -750,14 +785,14 @@ private async Task LoadFromResultSetAsync(DbDataReader rs, int i, object obj, st
750785
/// <summary>
751786
/// Determine the concrete class of an instance for the <c>DbDataReader</c>
752787
/// </summary>
753-
private async Task<string> GetInstanceClassAsync(DbDataReader rs, int i, ILoadable persister, object id, ISessionImplementor session, CancellationToken cancellationToken)
788+
private static async Task<string> GetInstanceClassAsync(DbDataReader rs, ILoadable persister, object id, ISessionImplementor session, IEntityAliases entityAliases, CancellationToken cancellationToken)
754789
{
755790
cancellationToken.ThrowIfCancellationRequested();
756791
if (persister.HasSubclasses)
757792
{
758793
// code to handle subclasses of topClass
759794
object discriminatorValue =
760-
await (persister.DiscriminatorType.NullSafeGetAsync(rs, EntityAliases[i].SuffixedDiscriminatorAlias, session, null, cancellationToken)).ConfigureAwait(false);
795+
await (persister.DiscriminatorType.NullSafeGetAsync(rs, entityAliases.SuffixedDiscriminatorAlias, session, null, cancellationToken)).ConfigureAwait(false);
761796

762797
string result = persister.GetSubclassForDiscriminatorValue(discriminatorValue);
763798

src/NHibernate/Criterion/EntityProjection.cs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Linq;
33
using NHibernate.Engine;
4+
using NHibernate.Loader;
45
using NHibernate.SqlCommand;
56
using NHibernate.Type;
67
using IQueryable = NHibernate.Persister.Entity.IQueryable;
@@ -17,7 +18,6 @@ public class EntityProjection : IProjection
1718
private string _columnAliasSuffix;
1819
private string _tableAlias;
1920
private IType[] _types;
20-
private string[] _columnAliases;
2121

2222
/// <summary>
2323
/// Root entity projection
@@ -53,9 +53,10 @@ public EntityProjection(System.Type rootEntity, string entityAlias)
5353
public bool Lazy { get; set; }
5454

5555
internal string[] IdentifierColumnAliases { get; private set; }
56-
internal string[][] PropertyColumnAliases { get; private set; }
56+
internal IEntityAliases EntityAliases { get; set; }
5757
internal IQueryable Persister { get; private set; }
5858
internal System.Type RootEntity { get; private set; }
59+
internal string[] ColumnAliases { get; private set; }
5960

6061
#region Configuration methods
6162

@@ -130,14 +131,14 @@ string[] IProjection.GetColumnAliases(int position, ICriteria criteria, ICriteri
130131
{
131132
SetFields(criteria, criteriaQuery);
132133

133-
return _columnAliases;
134+
return ColumnAliases;
134135
}
135136

136137
string[] IProjection.GetColumnAliases(string alias, int position, ICriteria criteria, ICriteriaQuery criteriaQuery)
137138
{
138139
SetFields(criteria, criteriaQuery);
139140

140-
return _columnAliases;
141+
return ColumnAliases;
141142
}
142143

143144
SqlString IProjection.ToSqlString(ICriteria criteria, int position, ICriteriaQuery criteriaQuery)
@@ -195,13 +196,17 @@ private void SetFields(ICriteria criteria, ICriteriaQuery criteriaQuery)
195196

196197
_columnAliasSuffix = criteriaQuery.GetIndexForAlias().ToString();
197198

198-
IdentifierColumnAliases = Persister.GetIdentifierAliases(_columnAliasSuffix);
199-
200-
PropertyColumnAliases = Lazy
201-
? new string[][] { }
202-
: Enumerable.Range(0, Persister.PropertyNames.Length).Select(i => Persister.GetPropertyAliases(_columnAliasSuffix, i)).ToArray();
203-
204-
_columnAliases = IdentifierColumnAliases.Concat(PropertyColumnAliases.SelectMany(ca => ca)).ToArray();
199+
if (Lazy)
200+
{
201+
IdentifierColumnAliases = Persister.GetIdentifierAliases(_columnAliasSuffix);
202+
}
203+
else
204+
{
205+
EntityAliases = new DefaultEntityAliases(Persister, _columnAliasSuffix);
206+
IdentifierColumnAliases = EntityAliases.SuffixedKeyAliases;
207+
}
208+
209+
ColumnAliases = IdentifierColumnAliases.Concat(EntityAliases?.SuffixedPropertyAliases.SelectMany(ca => ca) ?? Array.Empty<string>()).ToArray();
205210

206211
_types = new IType[] {new EntityProjectionType(this)};
207212
}

0 commit comments

Comments
 (0)