From 4f8c9725905d4d035a1a9b738221dca0eae68708 Mon Sep 17 00:00:00 2001 From: maca88 Date: Tue, 15 Jan 2019 20:29:52 +0100 Subject: [PATCH 1/4] Refactor sequential select --- src/NHibernate/Async/Loader/Loader.cs | 2 +- .../Entity/AbstractEntityPersister.cs | 36 ++++--- .../Async/Persister/Entity/ILoadable.cs | 40 +++++++ src/NHibernate/Loader/Loader.cs | 2 +- .../Entity/AbstractEntityPersister.cs | 101 +++++++++++++++--- src/NHibernate/Persister/Entity/ILoadable.cs | 29 +++++ .../Entity/SingleTableEntityPersister.cs | 94 ++++------------ 7 files changed, 202 insertions(+), 102 deletions(-) diff --git a/src/NHibernate/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index 537918795db..83f29265e96 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -793,7 +793,7 @@ private async Task LoadFromResultSetAsync(DbDataReader rs, int i, object obj, st ? EntityAliases[i].SuffixedPropertyAliases : GetSubclassEntityAliases(i, persister); - object[] values = await (persister.HydrateAsync(rs, id, obj, rootPersister, cols, eagerPropertyFetch, session, cancellationToken)).ConfigureAwait(false); + object[] values = await (persister.HydrateAsync(rs, id, obj, cols, eagerPropertyFetch, session, cancellationToken)).ConfigureAwait(false); object rowId = persister.HasRowId ? rs[EntityAliases[i].RowIdAlias] : null; diff --git a/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs index ae54caf3fcb..b29394f0a5c 100644 --- a/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs @@ -36,6 +36,7 @@ using Property=NHibernate.Mapping.Property; using NHibernate.SqlTypes; using System.Linq; +using System.Linq.Expressions; using NHibernate.Bytecode; namespace NHibernate.Persister.Entity @@ -339,7 +340,21 @@ protected async Task DehydrateAsync(object id, object[] fields, object rowI /// Unmarshall the fields of a persistent instance from a result set, /// without resolving associations or collections /// - public async Task HydrateAsync(DbDataReader rs, object id, object obj, ILoadable rootLoadable, + public Task HydrateAsync(DbDataReader rs, object id, object obj, ILoadable rootLoadable, + string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + return HydrateAsync(rs, id, obj, suffixedPropertyColumns, allProperties, session, cancellationToken); + } + + /// + /// Unmarshall the fields of a persistent instance from a result set, + /// without resolving associations or collections + /// + public async Task HydrateAsync(DbDataReader rs, object id, object obj, string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -348,9 +363,7 @@ public async Task HydrateAsync(DbDataReader rs, object id, object obj, log.Debug("Hydrating entity: {0}", MessageHelper.InfoString(this, id, Factory)); } - AbstractEntityPersister rootPersister = (AbstractEntityPersister)rootLoadable; - - bool hasDeferred = rootPersister.HasSequentialSelect; + bool hasDeferred = HasSequentialSelect; DbCommand sequentialSelect = null; DbDataReader sequentialResultSet = null; bool sequentialSelectEmpty = false; @@ -359,12 +372,12 @@ public async Task HydrateAsync(DbDataReader rs, object id, object obj, { if (hasDeferred) { - SqlString sql = rootPersister.GetSequentialSelect(EntityName); + var sql = GetSequentialSelect(); if (sql != null) { //TODO: I am not so sure about the exception handling in this bit! sequentialSelect = await (session.Batcher.PrepareCommandAsync(CommandType.Text, sql, IdentifierType.SqlTypes(factory), cancellationToken)).ConfigureAwait(false); - await (rootPersister.IdentifierType.NullSafeSetAsync(sequentialSelect, id, 0, session, cancellationToken)).ConfigureAwait(false); + await (IdentifierType.NullSafeSetAsync(sequentialSelect, id, 0, session, cancellationToken)).ConfigureAwait(false); sequentialResultSet = await (session.Batcher.ExecuteReaderAsync(sequentialSelect, cancellationToken)).ConfigureAwait(false); if (!await (sequentialResultSet.ReadAsync(cancellationToken)).ConfigureAwait(false)) { @@ -394,11 +407,9 @@ public async Task HydrateAsync(DbDataReader rs, object id, object obj, } } - string[] propNames = PropertyNames; IType[] types = PropertyTypes; object[] values = new object[types.Length]; bool[] laziness = PropertyLaziness; - string[] propSubclassNames = SubclassPropertySubclassNameClosure; for (int i = 0; i < types.Length; i++) { @@ -409,8 +420,7 @@ public async Task HydrateAsync(DbDataReader rs, object id, object obj, else if (allProperties || !laziness[i]) { //decide which ResultSet to get the property value from: - bool propertyIsDeferred = hasDeferred - && rootPersister.IsSubclassPropertyDeferred(propNames[i], propSubclassNames[i]); + var propertyIsDeferred = hasDeferred && IsPropertyDeferred(i); if (propertyIsDeferred && sequentialSelectEmpty) { values[i] = null; @@ -428,15 +438,11 @@ public async Task HydrateAsync(DbDataReader rs, object id, object obj, } } - if (sequentialResultSet != null) - { - sequentialResultSet.Close(); - } - return values; } finally { + sequentialResultSet?.Close(); if (sequentialSelect != null) { session.Batcher.CloseCommand(sequentialSelect, sequentialResultSet); diff --git a/src/NHibernate/Async/Persister/Entity/ILoadable.cs b/src/NHibernate/Async/Persister/Entity/ILoadable.cs index 2d74dae80a4..050ec42ec0e 100644 --- a/src/NHibernate/Async/Persister/Entity/ILoadable.cs +++ b/src/NHibernate/Async/Persister/Entity/ILoadable.cs @@ -8,6 +8,7 @@ //------------------------------------------------------------------------------ +using System; using NHibernate.Type; using NHibernate.Engine; using System.Data.Common; @@ -23,7 +24,46 @@ public partial interface ILoadable : IEntityPersister /// /// Retrieve property values from one row of a result set /// + //Since 5.3 + [Obsolete("Use the extension method without the rootLoadable parameter instead")] Task HydrateAsync(DbDataReader rs, object id, object obj, ILoadable rootLoadable, string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session, CancellationToken cancellationToken); } + + public static partial class LoadableExtensions + { + public static Task HydrateAsync( + this ILoadable loadable, + DbDataReader rs, + object id, + object obj, + string[][] suffixedPropertyColumns, + bool allProperties, + ISessionImplementor session, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + try + { + if (loadable is AbstractEntityPersister entityPersister) + { + return entityPersister.HydrateAsync(rs, id, obj, suffixedPropertyColumns, allProperties, session, cancellationToken); + } + + var rootLoadable = loadable.RootEntityName == loadable.EntityName + ? loadable + : (ILoadable) loadable.Factory.GetEntityPersister(loadable.RootEntityName); + +#pragma warning disable 618 + return loadable.HydrateAsync(rs, id, obj, rootLoadable, suffixedPropertyColumns, allProperties, session, cancellationToken); +#pragma warning restore 618 + } + catch (Exception ex) + { + return Task.FromException(ex); + } + } + } } diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index 0615518a3aa..0628bd7ab18 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -1144,7 +1144,7 @@ private void LoadFromResultSet(DbDataReader rs, int i, object obj, string instan ? EntityAliases[i].SuffixedPropertyAliases : GetSubclassEntityAliases(i, persister); - object[] values = persister.Hydrate(rs, id, obj, rootPersister, cols, eagerPropertyFetch, session); + object[] values = persister.Hydrate(rs, id, obj, cols, eagerPropertyFetch, session); object rowId = persister.HasRowId ? rs[EntityAliases[i].RowIdAlias] : null; diff --git a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs index 85d98154acd..bb16a7fcc65 100644 --- a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs @@ -26,6 +26,7 @@ using Property=NHibernate.Mapping.Property; using NHibernate.SqlTypes; using System.Linq; +using System.Linq.Expressions; using NHibernate.Bytecode; namespace NHibernate.Persister.Entity @@ -258,6 +259,7 @@ public virtual void BindValues(DbCommand ps) // This must be a Lazy, because instances of this class must be thread safe. private readonly Lazy defaultUniqueKeyPropertyNamesForSelectId; + private readonly Dictionary propertyTableNumbersByNameAndSubclass = new Dictionary(); protected AbstractEntityPersister(PersistentClass persistentClass, ICacheConcurrencyStrategy cache, ISessionFactoryImplementor factory) @@ -443,6 +445,8 @@ protected AbstractEntityPersister(PersistentClass persistentClass, ICacheConcurr definedBySubclass.Add(isDefinedBySubclass); propNullables.Add(prop.IsOptional || isDefinedBySubclass); //TODO: is this completely correct? types.Add(prop.Type); + propertyTableNumbersByNameAndSubclass[prop.PersistentClass.EntityName + '.' + prop.Name] = + persistentClass.GetJoinNumber(prop); string[] cols = new string[prop.ColumnSpan]; string[] forms = new string[prop.ColumnSpan]; @@ -1109,6 +1113,16 @@ protected virtual bool IsIdOfTable(int property, int table) protected abstract int GetSubclassPropertyTableNumber(int i); + internal int GetSubclassPropertyTableNumber(string propertyName, string entityName) + { + IType type = propertyMapping.ToType(propertyName); + if (type.IsAssociationType && ((IAssociationType) type).UseLHSPrimaryKey) + return 0; + int tabnum; + propertyTableNumbersByNameAndSubclass.TryGetValue(entityName + '.' + propertyName, out tabnum); + return tabnum; + } + public abstract string FilterFragment(string alias); protected internal virtual string DiscriminatorAlias @@ -1161,11 +1175,18 @@ protected bool IsDeleteCallable(int j) return deleteCallable[j]; } + //Since 5.3 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] protected virtual bool IsSubclassPropertyDeferred(string propertyName, string entityName) { return false; } + protected virtual bool IsPropertyDeferred(int propertyIndex) + { + return false; + } + protected virtual bool IsSubclassTableSequentialSelect(int table) { return false; @@ -2590,15 +2611,23 @@ protected int Dehydrate(object id, object[] fields, object rowId, bool[] include /// public object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootLoadable, string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session) + { + return Hydrate(rs, id, obj, suffixedPropertyColumns, allProperties, session); + } + + /// + /// Unmarshall the fields of a persistent instance from a result set, + /// without resolving associations or collections + /// + public object[] Hydrate(DbDataReader rs, object id, object obj, + string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session) { if (log.IsDebugEnabled()) { log.Debug("Hydrating entity: {0}", MessageHelper.InfoString(this, id, Factory)); } - AbstractEntityPersister rootPersister = (AbstractEntityPersister)rootLoadable; - - bool hasDeferred = rootPersister.HasSequentialSelect; + bool hasDeferred = HasSequentialSelect; DbCommand sequentialSelect = null; DbDataReader sequentialResultSet = null; bool sequentialSelectEmpty = false; @@ -2607,12 +2636,12 @@ public object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootLo { if (hasDeferred) { - SqlString sql = rootPersister.GetSequentialSelect(EntityName); + var sql = GetSequentialSelect(); if (sql != null) { //TODO: I am not so sure about the exception handling in this bit! sequentialSelect = session.Batcher.PrepareCommand(CommandType.Text, sql, IdentifierType.SqlTypes(factory)); - rootPersister.IdentifierType.NullSafeSet(sequentialSelect, id, 0, session); + IdentifierType.NullSafeSet(sequentialSelect, id, 0, session); sequentialResultSet = session.Batcher.ExecuteReader(sequentialSelect); if (!sequentialResultSet.Read()) { @@ -2642,11 +2671,9 @@ public object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootLo } } - string[] propNames = PropertyNames; IType[] types = PropertyTypes; object[] values = new object[types.Length]; bool[] laziness = PropertyLaziness; - string[] propSubclassNames = SubclassPropertySubclassNameClosure; for (int i = 0; i < types.Length; i++) { @@ -2657,8 +2684,7 @@ public object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootLo else if (allProperties || !laziness[i]) { //decide which ResultSet to get the property value from: - bool propertyIsDeferred = hasDeferred - && rootPersister.IsSubclassPropertyDeferred(propNames[i], propSubclassNames[i]); + var propertyIsDeferred = hasDeferred && IsPropertyDeferred(i); if (propertyIsDeferred && sequentialSelectEmpty) { values[i] = null; @@ -2676,15 +2702,11 @@ public object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootLo } } - if (sequentialResultSet != null) - { - sequentialResultSet.Close(); - } - return values; } finally { + sequentialResultSet?.Close(); if (sequentialSelect != null) { session.Batcher.CloseCommand(sequentialSelect, sequentialResultSet); @@ -2702,11 +2724,18 @@ protected bool UseGetGeneratedKeys() return Factory.Settings.IsGetGeneratedKeysEnabled; } + //Since 5.3 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] protected virtual SqlString GetSequentialSelect(string entityName) { throw new NotSupportedException("no sequential selects"); } + protected virtual SqlString GetSequentialSelect() + { + throw new NotSupportedException("no sequential select"); + } + /// /// Perform an SQL INSERT, and then retrieve a generated identifier. /// @@ -4491,5 +4520,49 @@ public string GetInfoString() return MessageHelper.InfoString(this); } #endregion + + internal SqlString GenerateSequentialSelect(ILoadable persister) + { + //figure out which tables need to be fetched (only those that contains at least a no-lazy-property) + AbstractEntityPersister subclassPersister = (AbstractEntityPersister) persister; + var tableNumbers = new HashSet(); + string[] props = subclassPersister.PropertyNames; + string[] classes = subclassPersister.PropertySubclassNames; + for (int i = 0; i < props.Length; i++) + { + int propTableNumber = GetSubclassPropertyTableNumber(props[i], classes[i]); + if (IsSubclassTableSequentialSelect(propTableNumber) && !IsSubclassTableLazy(propTableNumber)) + { + tableNumbers.Add(propTableNumber); + } + } + if ((tableNumbers.Count == 0)) + return null; + + //figure out which columns are needed (excludes lazy-properties) + List columnNumbers = new List(); + int[] columnTableNumbers = SubclassColumnTableNumberClosure; + for (int i = 0; i < SubclassColumnClosure.Length; i++) + { + if (tableNumbers.Contains(columnTableNumbers[i])) + { + columnNumbers.Add(i); + } + } + + //figure out which formulas are needed (excludes lazy-properties) + List formulaNumbers = new List(); + int[] formulaTableNumbers = SubclassFormulaTableNumberClosure; + for (int i = 0; i < SubclassFormulaTemplateClosure.Length; i++) + { + if (tableNumbers.Contains(formulaTableNumbers[i])) + { + formulaNumbers.Add(i); + } + } + + //render the SQL + return RenderSelect(tableNumbers.ToArray(), columnNumbers.ToArray(), formulaNumbers.ToArray()); + } } } diff --git a/src/NHibernate/Persister/Entity/ILoadable.cs b/src/NHibernate/Persister/Entity/ILoadable.cs index 22bea023e84..60be612c398 100644 --- a/src/NHibernate/Persister/Entity/ILoadable.cs +++ b/src/NHibernate/Persister/Entity/ILoadable.cs @@ -1,3 +1,4 @@ +using System; using NHibernate.Type; using NHibernate.Engine; using System.Data.Common; @@ -68,7 +69,35 @@ public partial interface ILoadable : IEntityPersister /// /// Retrieve property values from one row of a result set /// + //Since 5.3 + [Obsolete("Use the extension method without the rootLoadable parameter instead")] object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootLoadable, string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session); } + + public static partial class LoadableExtensions + { + public static object[] Hydrate( + this ILoadable loadable, + DbDataReader rs, + object id, + object obj, + string[][] suffixedPropertyColumns, + bool allProperties, + ISessionImplementor session) + { + if (loadable is AbstractEntityPersister entityPersister) + { + return entityPersister.Hydrate(rs, id, obj, suffixedPropertyColumns, allProperties, session); + } + + var rootLoadable = loadable.RootEntityName == loadable.EntityName + ? loadable + : (ILoadable) loadable.Factory.GetEntityPersister(loadable.RootEntityName); + +#pragma warning disable 618 + return loadable.Hydrate(rs, id, obj, rootLoadable, suffixedPropertyColumns, allProperties, session); +#pragma warning restore 618 + } + } } diff --git a/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs b/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs index 89a7423d174..285fc12c40b 100644 --- a/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs @@ -66,9 +66,8 @@ public class SingleTableEntityPersister : AbstractEntityPersister, IQueryable private readonly string[][] constraintOrderedKeyColumnNames; //private readonly IDictionary propertyTableNumbersByName = new Hashtable(); - private readonly Dictionary propertyTableNumbersByNameAndSubclass = new Dictionary(); - private readonly Dictionary sequentialSelectStringsByEntityName = new Dictionary(); + private SqlString _sequentialSelectString; private static readonly object NullDiscriminator = new object(); private static readonly object NotNullDiscriminator = new object(); @@ -192,7 +191,7 @@ public SingleTableEntityPersister(PersistentClass persistentClass, ICacheConcurr isInverses.Add(join.IsInverse); isNullables.Add(join.IsOptional); isLazies.Add(lazyAvailable && join.IsLazy); - if (join.IsSequentialSelect && !persistentClass.IsClassOrSuperclassJoin(join)) + if (join.IsSequentialSelect) hasDeferred = true; subclassTables.Add(join.Table.GetQualifiedName(factory.Dialect, factory.Settings.DefaultCatalogName, factory.Settings.DefaultSchemaName)); @@ -329,7 +328,6 @@ public SingleTableEntityPersister(PersistentClass persistentClass, ICacheConcurr int join = persistentClass.GetJoinNumber(prop); propertyJoinNumbers.Add(join); - propertyTableNumbersByNameAndSubclass[prop.PersistentClass.EntityName + '.' + prop.Name] = join; foreach (ISelectable thing in prop.ColumnIterator) { if (thing.IsFormula) @@ -690,10 +688,17 @@ protected override void AddDiscriminatorToInsert(SqlInsertBuilder insert) insert.AddColumn(DiscriminatorColumnName, DiscriminatorSQLValue); } + //Since 5.3 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] protected override bool IsSubclassPropertyDeferred(string propertyName, string entityName) { return - hasSequentialSelects && IsSubclassTableSequentialSelect(GetSubclassPropertyTableNumber(propertyName, entityName)); + hasSequentialSelects && IsSubclassTableSequentialSelect(base.GetSubclassPropertyTableNumber(propertyName, entityName)); + } + + protected override bool IsPropertyDeferred(int propertyIndex) + { + return hasSequentialSelects && subclassTableSequentialSelect[GetSubclassPropertyTableNumber(propertyIndex)]; } public override bool HasSequentialSelect @@ -701,68 +706,24 @@ public override bool HasSequentialSelect get { return hasSequentialSelects; } } - public int GetSubclassPropertyTableNumber(string propertyName, string entityName) + //Since 5.3 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] + public new int GetSubclassPropertyTableNumber(string propertyName, string entityName) { - IType type = propertyMapping.ToType(propertyName); - if (type.IsAssociationType && ((IAssociationType)type).UseLHSPrimaryKey) - return 0; - int tabnum; - propertyTableNumbersByNameAndSubclass.TryGetValue(entityName + '.' + propertyName, out tabnum); - return tabnum; + return base.GetSubclassPropertyTableNumber(propertyName, entityName); } + //Since 5.3 + [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] protected override SqlString GetSequentialSelect(string entityName) { - SqlString result; - sequentialSelectStringsByEntityName.TryGetValue(entityName, out result); - return result; + var persister = Factory.GetEntityPersister(entityName) as SingleTableEntityPersister; + return persister?.GetSequentialSelect(); } - private SqlString GenerateSequentialSelect(ILoadable persister) + protected override SqlString GetSequentialSelect() { - //note that this method could easily be moved up to BasicEntityPersister, - //if we ever needed to reuse it from other subclasses - - //figure out which tables need to be fetched (only those that contains at least a no-lazy-property) - AbstractEntityPersister subclassPersister = (AbstractEntityPersister)persister; - var tableNumbers = new HashSet(); - string[] props = subclassPersister.PropertyNames; - string[] classes = subclassPersister.PropertySubclassNames; - for (int i = 0; i < props.Length; i++) - { - int propTableNumber = GetSubclassPropertyTableNumber(props[i], classes[i]); - if (IsSubclassTableSequentialSelect(propTableNumber) && !IsSubclassTableLazy(propTableNumber)) - { - tableNumbers.Add(propTableNumber); - } - } - if ((tableNumbers.Count == 0)) - return null; - - //figure out which columns are needed (excludes lazy-properties) - List columnNumbers = new List(); - int[] columnTableNumbers = SubclassColumnTableNumberClosure; - for (int i = 0; i < SubclassColumnClosure.Length; i++) - { - if (tableNumbers.Contains(columnTableNumbers[i])) - { - columnNumbers.Add(i); - } - } - - //figure out which formulas are needed (excludes lazy-properties) - List formulaNumbers = new List(); - int[] formulaTableNumbers = SubclassFormulaTableNumberClosure; - for (int i = 0; i < SubclassFormulaTemplateClosure.Length; i++) - { - if (tableNumbers.Contains(formulaTableNumbers[i])) - { - formulaNumbers.Add(i); - } - } - - //render the SQL - return RenderSelect(tableNumbers.ToArray(), columnNumbers.ToArray(), formulaNumbers.ToArray()); + return _sequentialSelectString; } //provide columns to join to if the key is other than the primary key @@ -820,19 +781,10 @@ public override string GetPropertyTableName(string propertyName) public override void PostInstantiate() { base.PostInstantiate(); - if (hasSequentialSelects) + if (hasSequentialSelects && !IsAbstract) { - string[] entityNames = SubclassClosure; - for (int i = 1; i < entityNames.Length; i++) - { - ILoadable loadable = (ILoadable)Factory.GetEntityPersister(entityNames[i]); - if (!loadable.IsAbstract) - { - //perhaps not really necessary... - SqlString sequentialSelect = GenerateSequentialSelect(loadable); - sequentialSelectStringsByEntityName[entityNames[i]] = sequentialSelect; - } - } + var rootLoadable = (AbstractEntityPersister) Factory.GetEntityPersister(RootEntityName); + _sequentialSelectString = rootLoadable.GenerateSequentialSelect(this); } } } From 6484c2b5c34d852c44ea2a819b05acaaae4570ae Mon Sep 17 00:00:00 2001 From: maca88 Date: Wed, 16 Jan 2019 20:35:04 +0100 Subject: [PATCH 2/4] Minor corrections --- .../Entity/AbstractEntityPersister.cs | 34 +++++++++---------- src/NHibernate/Persister/Entity/ILoadable.cs | 2 +- .../Entity/SingleTableEntityPersister.cs | 6 ++-- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs index bb16a7fcc65..8b2d1f7c142 100644 --- a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs @@ -1115,11 +1115,10 @@ protected virtual bool IsIdOfTable(int property, int table) internal int GetSubclassPropertyTableNumber(string propertyName, string entityName) { - IType type = propertyMapping.ToType(propertyName); + var type = propertyMapping.ToType(propertyName); if (type.IsAssociationType && ((IAssociationType) type).UseLHSPrimaryKey) return 0; - int tabnum; - propertyTableNumbersByNameAndSubclass.TryGetValue(entityName + '.' + propertyName, out tabnum); + propertyTableNumbersByNameAndSubclass.TryGetValue(entityName + '.' + propertyName, out var tabnum); return tabnum; } @@ -1175,7 +1174,7 @@ protected bool IsDeleteCallable(int j) return deleteCallable[j]; } - //Since 5.3 + //Since v5.3 [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] protected virtual bool IsSubclassPropertyDeferred(string propertyName, string entityName) { @@ -2724,7 +2723,7 @@ protected bool UseGetGeneratedKeys() return Factory.Settings.IsGetGeneratedKeysEnabled; } - //Since 5.3 + //Since v5.3 [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] protected virtual SqlString GetSequentialSelect(string entityName) { @@ -4521,28 +4520,27 @@ public string GetInfoString() } #endregion - internal SqlString GenerateSequentialSelect(ILoadable persister) + internal SqlString GenerateSequentialSelect(AbstractEntityPersister subclassPersister) { //figure out which tables need to be fetched (only those that contains at least a no-lazy-property) - AbstractEntityPersister subclassPersister = (AbstractEntityPersister) persister; var tableNumbers = new HashSet(); - string[] props = subclassPersister.PropertyNames; - string[] classes = subclassPersister.PropertySubclassNames; - for (int i = 0; i < props.Length; i++) + var props = subclassPersister.PropertyNames; + var classes = subclassPersister.PropertySubclassNames; + for (var i = 0; i < props.Length; i++) { - int propTableNumber = GetSubclassPropertyTableNumber(props[i], classes[i]); + var propTableNumber = GetSubclassPropertyTableNumber(props[i], classes[i]); if (IsSubclassTableSequentialSelect(propTableNumber) && !IsSubclassTableLazy(propTableNumber)) { tableNumbers.Add(propTableNumber); } } - if ((tableNumbers.Count == 0)) + if (tableNumbers.Count == 0) return null; //figure out which columns are needed (excludes lazy-properties) - List columnNumbers = new List(); - int[] columnTableNumbers = SubclassColumnTableNumberClosure; - for (int i = 0; i < SubclassColumnClosure.Length; i++) + var columnNumbers = new List(); + var columnTableNumbers = SubclassColumnTableNumberClosure; + for (var i = 0; i < SubclassColumnClosure.Length; i++) { if (tableNumbers.Contains(columnTableNumbers[i])) { @@ -4551,9 +4549,9 @@ internal SqlString GenerateSequentialSelect(ILoadable persister) } //figure out which formulas are needed (excludes lazy-properties) - List formulaNumbers = new List(); - int[] formulaTableNumbers = SubclassFormulaTableNumberClosure; - for (int i = 0; i < SubclassFormulaTemplateClosure.Length; i++) + var formulaNumbers = new List(); + var formulaTableNumbers = SubclassFormulaTableNumberClosure; + for (var i = 0; i < SubclassFormulaTemplateClosure.Length; i++) { if (tableNumbers.Contains(formulaTableNumbers[i])) { diff --git a/src/NHibernate/Persister/Entity/ILoadable.cs b/src/NHibernate/Persister/Entity/ILoadable.cs index 60be612c398..10fbaced9ba 100644 --- a/src/NHibernate/Persister/Entity/ILoadable.cs +++ b/src/NHibernate/Persister/Entity/ILoadable.cs @@ -69,7 +69,7 @@ public partial interface ILoadable : IEntityPersister /// /// Retrieve property values from one row of a result set /// - //Since 5.3 + //Since v5.3 [Obsolete("Use the extension method without the rootLoadable parameter instead")] object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootLoadable, string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session); diff --git a/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs b/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs index 285fc12c40b..e82fc057b31 100644 --- a/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs @@ -688,7 +688,7 @@ protected override void AddDiscriminatorToInsert(SqlInsertBuilder insert) insert.AddColumn(DiscriminatorColumnName, DiscriminatorSQLValue); } - //Since 5.3 + //Since v5.3 [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] protected override bool IsSubclassPropertyDeferred(string propertyName, string entityName) { @@ -706,14 +706,14 @@ public override bool HasSequentialSelect get { return hasSequentialSelects; } } - //Since 5.3 + //Since v5.3 [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] public new int GetSubclassPropertyTableNumber(string propertyName, string entityName) { return base.GetSubclassPropertyTableNumber(propertyName, entityName); } - //Since 5.3 + //Since v5.3 [Obsolete("This method has no more usage in NHibernate and will be removed in a future version.")] protected override SqlString GetSequentialSelect(string entityName) { From 9d64cdee89839ac11b5df6e8dae304ad6e00e8ae Mon Sep 17 00:00:00 2001 From: maca88 Date: Wed, 16 Jan 2019 20:55:15 +0100 Subject: [PATCH 3/4] Avoid breaking change for HasSequentialSelect and deprecate it --- .../Entity/AbstractEntityPersister.cs | 64 ++++++++--------- .../Async/Persister/Entity/ILoadable.cs | 2 +- .../Entity/AbstractEntityPersister.cs | 68 +++++++++---------- .../Entity/SingleTableEntityPersister.cs | 13 ++-- 4 files changed, 73 insertions(+), 74 deletions(-) diff --git a/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs index b29394f0a5c..1fa357301cc 100644 --- a/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs @@ -363,47 +363,43 @@ public async Task HydrateAsync(DbDataReader rs, object id, object obj, log.Debug("Hydrating entity: {0}", MessageHelper.InfoString(this, id, Factory)); } - bool hasDeferred = HasSequentialSelect; + var sequentialSql = GetSequentialSelect(); DbCommand sequentialSelect = null; DbDataReader sequentialResultSet = null; bool sequentialSelectEmpty = false; using (session.BeginProcess()) try { - if (hasDeferred) + if (sequentialSql != null) { - var sql = GetSequentialSelect(); - if (sql != null) + //TODO: I am not so sure about the exception handling in this bit! + sequentialSelect = await (session.Batcher.PrepareCommandAsync(CommandType.Text, sequentialSql, IdentifierType.SqlTypes(factory), cancellationToken)).ConfigureAwait(false); + await (IdentifierType.NullSafeSetAsync(sequentialSelect, id, 0, session, cancellationToken)).ConfigureAwait(false); + sequentialResultSet = await (session.Batcher.ExecuteReaderAsync(sequentialSelect, cancellationToken)).ConfigureAwait(false); + if (!await (sequentialResultSet.ReadAsync(cancellationToken)).ConfigureAwait(false)) { - //TODO: I am not so sure about the exception handling in this bit! - sequentialSelect = await (session.Batcher.PrepareCommandAsync(CommandType.Text, sql, IdentifierType.SqlTypes(factory), cancellationToken)).ConfigureAwait(false); - await (IdentifierType.NullSafeSetAsync(sequentialSelect, id, 0, session, cancellationToken)).ConfigureAwait(false); - sequentialResultSet = await (session.Batcher.ExecuteReaderAsync(sequentialSelect, cancellationToken)).ConfigureAwait(false); - if (!await (sequentialResultSet.ReadAsync(cancellationToken)).ConfigureAwait(false)) - { - // TODO: Deal with the "optional" attribute in the mapping; - // this code assumes that optional defaults to "true" because it - // doesn't actually seem to work in the fetch="join" code - // - // Note that actual proper handling of optional-ality here is actually - // more involved than this patch assumes. Remember that we might have - // multiple mappings associated with a single entity. Really - // a couple of things need to happen to properly handle optional here: - // 1) First and foremost, when handling multiple s, we really - // should be using the entity root table as the driving table; - // another option here would be to choose some non-optional joined - // table to use as the driving table. In all likelihood, just using - // the root table is much simplier - // 2) Need to add the FK columns corresponding to each joined table - // to the generated select list; these would then be used when - // iterating the result set to determine whether all non-optional - // data is present - // My initial thoughts on the best way to deal with this would be - // to introduce a new SequentialSelect abstraction that actually gets - // generated in the persisters (ok, SingleTable...) and utilized here. - // It would encapsulated all this required optional-ality checking... - sequentialSelectEmpty = true; - } + // TODO: Deal with the "optional" attribute in the mapping; + // this code assumes that optional defaults to "true" because it + // doesn't actually seem to work in the fetch="join" code + // + // Note that actual proper handling of optional-ality here is actually + // more involved than this patch assumes. Remember that we might have + // multiple mappings associated with a single entity. Really + // a couple of things need to happen to properly handle optional here: + // 1) First and foremost, when handling multiple s, we really + // should be using the entity root table as the driving table; + // another option here would be to choose some non-optional joined + // table to use as the driving table. In all likelihood, just using + // the root table is much simplier + // 2) Need to add the FK columns corresponding to each joined table + // to the generated select list; these would then be used when + // iterating the result set to determine whether all non-optional + // data is present + // My initial thoughts on the best way to deal with this would be + // to introduce a new SequentialSelect abstraction that actually gets + // generated in the persisters (ok, SingleTable...) and utilized here. + // It would encapsulated all this required optional-ality checking... + sequentialSelectEmpty = true; } } @@ -420,7 +416,7 @@ public async Task HydrateAsync(DbDataReader rs, object id, object obj, else if (allProperties || !laziness[i]) { //decide which ResultSet to get the property value from: - var propertyIsDeferred = hasDeferred && IsPropertyDeferred(i); + var propertyIsDeferred = sequentialSql != null && IsPropertyDeferred(i); if (propertyIsDeferred && sequentialSelectEmpty) { values[i] = null; diff --git a/src/NHibernate/Async/Persister/Entity/ILoadable.cs b/src/NHibernate/Async/Persister/Entity/ILoadable.cs index 050ec42ec0e..0d975063abe 100644 --- a/src/NHibernate/Async/Persister/Entity/ILoadable.cs +++ b/src/NHibernate/Async/Persister/Entity/ILoadable.cs @@ -24,7 +24,7 @@ public partial interface ILoadable : IEntityPersister /// /// Retrieve property values from one row of a result set /// - //Since 5.3 + //Since v5.3 [Obsolete("Use the extension method without the rootLoadable parameter instead")] Task HydrateAsync(DbDataReader rs, object id, object obj, ILoadable rootLoadable, string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session, CancellationToken cancellationToken); diff --git a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs index 8b2d1f7c142..f1e3fc3632c 100644 --- a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs @@ -1191,6 +1191,8 @@ protected virtual bool IsSubclassTableSequentialSelect(int table) return false; } + //Since v5.3 + [Obsolete("This property has no more usage in NHibernate and will be removed in a future version.")] public virtual bool HasSequentialSelect { get { return false; } @@ -2626,47 +2628,43 @@ public object[] Hydrate(DbDataReader rs, object id, object obj, log.Debug("Hydrating entity: {0}", MessageHelper.InfoString(this, id, Factory)); } - bool hasDeferred = HasSequentialSelect; + var sequentialSql = GetSequentialSelect(); DbCommand sequentialSelect = null; DbDataReader sequentialResultSet = null; bool sequentialSelectEmpty = false; using (session.BeginProcess()) try { - if (hasDeferred) + if (sequentialSql != null) { - var sql = GetSequentialSelect(); - if (sql != null) + //TODO: I am not so sure about the exception handling in this bit! + sequentialSelect = session.Batcher.PrepareCommand(CommandType.Text, sequentialSql, IdentifierType.SqlTypes(factory)); + IdentifierType.NullSafeSet(sequentialSelect, id, 0, session); + sequentialResultSet = session.Batcher.ExecuteReader(sequentialSelect); + if (!sequentialResultSet.Read()) { - //TODO: I am not so sure about the exception handling in this bit! - sequentialSelect = session.Batcher.PrepareCommand(CommandType.Text, sql, IdentifierType.SqlTypes(factory)); - IdentifierType.NullSafeSet(sequentialSelect, id, 0, session); - sequentialResultSet = session.Batcher.ExecuteReader(sequentialSelect); - if (!sequentialResultSet.Read()) - { - // TODO: Deal with the "optional" attribute in the mapping; - // this code assumes that optional defaults to "true" because it - // doesn't actually seem to work in the fetch="join" code - // - // Note that actual proper handling of optional-ality here is actually - // more involved than this patch assumes. Remember that we might have - // multiple mappings associated with a single entity. Really - // a couple of things need to happen to properly handle optional here: - // 1) First and foremost, when handling multiple s, we really - // should be using the entity root table as the driving table; - // another option here would be to choose some non-optional joined - // table to use as the driving table. In all likelihood, just using - // the root table is much simplier - // 2) Need to add the FK columns corresponding to each joined table - // to the generated select list; these would then be used when - // iterating the result set to determine whether all non-optional - // data is present - // My initial thoughts on the best way to deal with this would be - // to introduce a new SequentialSelect abstraction that actually gets - // generated in the persisters (ok, SingleTable...) and utilized here. - // It would encapsulated all this required optional-ality checking... - sequentialSelectEmpty = true; - } + // TODO: Deal with the "optional" attribute in the mapping; + // this code assumes that optional defaults to "true" because it + // doesn't actually seem to work in the fetch="join" code + // + // Note that actual proper handling of optional-ality here is actually + // more involved than this patch assumes. Remember that we might have + // multiple mappings associated with a single entity. Really + // a couple of things need to happen to properly handle optional here: + // 1) First and foremost, when handling multiple s, we really + // should be using the entity root table as the driving table; + // another option here would be to choose some non-optional joined + // table to use as the driving table. In all likelihood, just using + // the root table is much simplier + // 2) Need to add the FK columns corresponding to each joined table + // to the generated select list; these would then be used when + // iterating the result set to determine whether all non-optional + // data is present + // My initial thoughts on the best way to deal with this would be + // to introduce a new SequentialSelect abstraction that actually gets + // generated in the persisters (ok, SingleTable...) and utilized here. + // It would encapsulated all this required optional-ality checking... + sequentialSelectEmpty = true; } } @@ -2683,7 +2681,7 @@ public object[] Hydrate(DbDataReader rs, object id, object obj, else if (allProperties || !laziness[i]) { //decide which ResultSet to get the property value from: - var propertyIsDeferred = hasDeferred && IsPropertyDeferred(i); + var propertyIsDeferred = sequentialSql != null && IsPropertyDeferred(i); if (propertyIsDeferred && sequentialSelectEmpty) { values[i] = null; @@ -2732,7 +2730,7 @@ protected virtual SqlString GetSequentialSelect(string entityName) protected virtual SqlString GetSequentialSelect() { - throw new NotSupportedException("no sequential select"); + return null; } /// diff --git a/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs b/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs index e82fc057b31..9aa8a71a3e4 100644 --- a/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs @@ -25,7 +25,8 @@ public class SingleTableEntityPersister : AbstractEntityPersister, IQueryable private readonly bool[] isNullableTable; private readonly string[][] keyColumnNames; private readonly bool[] cascadeDeleteEnabled; - private readonly bool hasSequentialSelects; + private readonly bool hasSequentialSelects; // TODO 6.0: Remove + private readonly bool _hasSequentialSelect; private readonly string[] spaces; private readonly string[] subclassClosure; private readonly string[] subclassTableNameClosure; @@ -164,7 +165,7 @@ public SingleTableEntityPersister(PersistentClass persistentClass, ICacheConcurr bool lazyAvailable = IsInstrumented; - bool hasDeferred = false; + bool hasDeferred = false; // TODO 6.0: Remove List subclassTables = new List(); List joinKeyColumns = new List(); //provided so we can join to keys other than the primary key @@ -192,6 +193,8 @@ public SingleTableEntityPersister(PersistentClass persistentClass, ICacheConcurr isNullables.Add(join.IsOptional); isLazies.Add(lazyAvailable && join.IsLazy); if (join.IsSequentialSelect) + _hasSequentialSelect = true; + if (join.IsSequentialSelect && !persistentClass.IsClassOrSuperclassJoin(join)) hasDeferred = true; subclassTables.Add(join.Table.GetQualifiedName(factory.Dialect, factory.Settings.DefaultCatalogName, factory.Settings.DefaultSchemaName)); @@ -698,9 +701,11 @@ protected override bool IsSubclassPropertyDeferred(string propertyName, string e protected override bool IsPropertyDeferred(int propertyIndex) { - return hasSequentialSelects && subclassTableSequentialSelect[GetSubclassPropertyTableNumber(propertyIndex)]; + return _hasSequentialSelect && subclassTableSequentialSelect[GetSubclassPropertyTableNumber(propertyIndex)]; } + //Since v5.3 + [Obsolete("This property has no more usage in NHibernate and will be removed in a future version.")] public override bool HasSequentialSelect { get { return hasSequentialSelects; } @@ -781,7 +786,7 @@ public override string GetPropertyTableName(string propertyName) public override void PostInstantiate() { base.PostInstantiate(); - if (hasSequentialSelects && !IsAbstract) + if (_hasSequentialSelect && !IsAbstract) { var rootLoadable = (AbstractEntityPersister) Factory.GetEntityPersister(RootEntityName); _sequentialSelectString = rootLoadable.GenerateSequentialSelect(this); From 07e02c47e06806c43e1587d8fd3ae456c18c40b9 Mon Sep 17 00:00:00 2001 From: maca88 Date: Tue, 22 Jan 2019 21:27:40 +0100 Subject: [PATCH 4/4] Obsolete AbstractEntityPersister.Hydrate method with the rootLoadable parameter --- .../Async/Persister/Entity/AbstractEntityPersister.cs | 2 ++ src/NHibernate/Persister/Entity/AbstractEntityPersister.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs index 1fa357301cc..f662587e6d0 100644 --- a/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs @@ -340,6 +340,8 @@ protected async Task DehydrateAsync(object id, object[] fields, object rowI /// Unmarshall the fields of a persistent instance from a result set, /// without resolving associations or collections /// + //Since v5.3 + [Obsolete("Use the overload without the rootLoadable parameter instead")] public Task HydrateAsync(DbDataReader rs, object id, object obj, ILoadable rootLoadable, string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session, CancellationToken cancellationToken) { diff --git a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs index f1e3fc3632c..87ed8a332d3 100644 --- a/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs +++ b/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs @@ -2610,6 +2610,8 @@ protected int Dehydrate(object id, object[] fields, object rowId, bool[] include /// Unmarshall the fields of a persistent instance from a result set, /// without resolving associations or collections /// + //Since v5.3 + [Obsolete("Use the overload without the rootLoadable parameter instead")] public object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootLoadable, string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session) {