diff --git a/src/NHibernate/Async/Loader/Loader.cs b/src/NHibernate/Async/Loader/Loader.cs index ca9136face0..9d37f545940 100644 --- a/src/NHibernate/Async/Loader/Loader.cs +++ b/src/NHibernate/Async/Loader/Loader.cs @@ -907,7 +907,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, eagerFetchProperties, eagerPropertyFetch, session, cancellationToken)).ConfigureAwait(false); + object[] values = await (persister.HydrateAsync(rs, id, obj, cols, eagerFetchProperties, 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 85c93b0a706..9746bba3833 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 @@ -101,7 +102,7 @@ async Task InternalInitializeLazyPropertiesAsync() } } - var values = await (HydrateAsync(rs, id, entity, rootPersister, suffixedPropertyColumns, null, true, indexes, session, cancellationToken)).ConfigureAwait(false); + var values = await (HydrateAsync(rs, id, entity, suffixedPropertyColumns, null, true, indexes, session, cancellationToken)).ConfigureAwait(false); for (var i = 0; i < lazyIndexes.Length; i++) { var value = values[i]; @@ -400,14 +401,14 @@ 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 Task HydrateAsync(DbDataReader rs, object id, object obj, ILoadable rootLoadable, + public Task HydrateAsync(DbDataReader rs, object id, object obj, string[][] suffixedPropertyColumns, ISet fetchedLazyProperties, bool allProperties, ISessionImplementor session, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled(cancellationToken); } - return HydrateAsync(rs, id, obj, rootLoadable, suffixedPropertyColumns, fetchedLazyProperties, allProperties, null, session, cancellationToken); + return HydrateAsync(rs, id, obj, suffixedPropertyColumns, fetchedLazyProperties, allProperties, null, session, cancellationToken); } /// @@ -423,14 +424,14 @@ public Task HydrateAsync(DbDataReader rs, object id, object obj, ILoad { return Task.FromCanceled(cancellationToken); } - return HydrateAsync(rs, id, obj, rootLoadable, suffixedPropertyColumns, null, allProperties, null, session, cancellationToken); + return HydrateAsync(rs, id, obj, suffixedPropertyColumns, null, allProperties, null, session, cancellationToken); } /// /// Unmarshall the fields of a persistent instance from a result set, /// without resolving associations or collections /// - private async Task HydrateAsync(DbDataReader rs, object id, object obj, ILoadable rootLoadable, string[][] suffixedPropertyColumns, + private async Task HydrateAsync(DbDataReader rs, object id, object obj, string[][] suffixedPropertyColumns, ISet fetchedLazyProperties, bool allProperties, int[] indexes, ISessionImplementor session, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -439,49 +440,43 @@ private 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; + var sequentialSql = GetSequentialSelect(); DbCommand sequentialSelect = null; DbDataReader sequentialResultSet = null; bool sequentialSelectEmpty = false; using (session.BeginProcess()) try { - if (hasDeferred) + if (sequentialSql != null) { - SqlString sql = rootPersister.GetSequentialSelect(EntityName); - 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 (rootPersister.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; } } @@ -491,7 +486,6 @@ private async Task HydrateAsync(DbDataReader rs, object id, object obj IType[] types = PropertyTypes; object[] values = new object[length]; bool[] laziness = PropertyLaziness; - string[] propSubclassNames = SubclassPropertySubclassNameClosure; for (int j = 0; j < length; j++) { @@ -503,8 +497,7 @@ private async Task HydrateAsync(DbDataReader rs, object id, object obj else if (allProperties || !laziness[i] || fetchedLazyProperties?.Contains(propNames[i]) == true) { //decide which ResultSet to get the property value from: - bool propertyIsDeferred = hasDeferred - && rootPersister.IsSubclassPropertyDeferred(propNames[i], propSubclassNames[i]); + var propertyIsDeferred = sequentialSql != null && IsPropertyDeferred(i); if (propertyIsDeferred && sequentialSelectEmpty) { values[j] = null; @@ -522,15 +515,11 @@ private 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 ac547d0eabc..8d164a06685 100644 --- a/src/NHibernate/Async/Persister/Entity/ILoadable.cs +++ b/src/NHibernate/Async/Persister/Entity/ILoadable.cs @@ -38,7 +38,7 @@ public static partial class LoadableExtensions /// //6.0 TODO: Merge into ILoadable public static Task HydrateAsync( - this ILoadable loadable, DbDataReader rs, object id, object obj, ILoadable rootLoadable, + this ILoadable loadable, DbDataReader rs, object id, object obj, string[][] suffixedPropertyColumns, ISet fetchedLazyProperties, bool allProperties, ISessionImplementor session, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) @@ -50,8 +50,12 @@ public static Task HydrateAsync( if (loadable is AbstractEntityPersister abstractEntityPersister) { return abstractEntityPersister.HydrateAsync( - rs, id, obj, rootLoadable, suffixedPropertyColumns, fetchedLazyProperties, allProperties, session, cancellationToken); + rs, id, obj, suffixedPropertyColumns, fetchedLazyProperties, allProperties, session, cancellationToken); } + + var rootLoadable = loadable.RootEntityName == loadable.EntityName + ? loadable + : (ILoadable) loadable.Factory.GetEntityPersister(loadable.RootEntityName); #pragma warning disable 618 // Fallback to the old behavior diff --git a/src/NHibernate/Loader/Loader.cs b/src/NHibernate/Loader/Loader.cs index 8849605899c..b79f8a7805d 100644 --- a/src/NHibernate/Loader/Loader.cs +++ b/src/NHibernate/Loader/Loader.cs @@ -1271,7 +1271,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, eagerFetchProperties, eagerPropertyFetch, session); + object[] values = persister.Hydrate(rs, id, obj, cols, eagerFetchProperties, 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 984494ab9c4..c741f7cc912 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,15 @@ protected virtual bool IsIdOfTable(int property, int table) protected abstract int GetSubclassPropertyTableNumber(int i); + internal int GetSubclassPropertyTableNumber(string propertyName, string entityName) + { + var type = propertyMapping.ToType(propertyName); + if (type.IsAssociationType && ((IAssociationType) type).UseLHSPrimaryKey) + return 0; + propertyTableNumbersByNameAndSubclass.TryGetValue(entityName + '.' + propertyName, out var tabnum); + return tabnum; + } + public abstract string FilterFragment(string alias); protected internal virtual string DiscriminatorAlias @@ -1161,16 +1174,25 @@ protected bool IsDeleteCallable(int j) return deleteCallable[j]; } + //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) { return false; } + protected virtual bool IsPropertyDeferred(int propertyIndex) + { + return false; + } + 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; } @@ -1338,7 +1360,7 @@ public void InitializeLazyProperties( } } - var values = Hydrate(rs, id, entity, rootPersister, suffixedPropertyColumns, null, true, indexes, session); + var values = Hydrate(rs, id, entity, suffixedPropertyColumns, null, true, indexes, session); for (var i = 0; i < lazyIndexes.Length; i++) { var value = values[i]; @@ -2685,10 +2707,10 @@ 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 /// - public object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootLoadable, + public object[] Hydrate(DbDataReader rs, object id, object obj, string[][] suffixedPropertyColumns, ISet fetchedLazyProperties, bool allProperties, ISessionImplementor session) { - return Hydrate(rs, id, obj, rootLoadable, suffixedPropertyColumns, fetchedLazyProperties, allProperties, null, session); + return Hydrate(rs, id, obj, suffixedPropertyColumns, fetchedLazyProperties, allProperties, null, session); } /// @@ -2700,14 +2722,14 @@ public object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootLo public object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootLoadable, string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session) { - return Hydrate(rs, id, obj, rootLoadable, suffixedPropertyColumns, null, allProperties, null, session); + return Hydrate(rs, id, obj, suffixedPropertyColumns, null, allProperties, null, session); } /// /// Unmarshall the fields of a persistent instance from a result set, /// without resolving associations or collections /// - private object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootLoadable, string[][] suffixedPropertyColumns, + private object[] Hydrate(DbDataReader rs, object id, object obj, string[][] suffixedPropertyColumns, ISet fetchedLazyProperties, bool allProperties, int[] indexes, ISessionImplementor session) { if (log.IsDebugEnabled()) @@ -2715,49 +2737,43 @@ private object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootL log.Debug("Hydrating entity: {0}", MessageHelper.InfoString(this, id, Factory)); } - AbstractEntityPersister rootPersister = (AbstractEntityPersister)rootLoadable; - - bool hasDeferred = rootPersister.HasSequentialSelect; + var sequentialSql = GetSequentialSelect(); DbCommand sequentialSelect = null; DbDataReader sequentialResultSet = null; bool sequentialSelectEmpty = false; using (session.BeginProcess()) try { - if (hasDeferred) + if (sequentialSql != null) { - SqlString sql = rootPersister.GetSequentialSelect(EntityName); - 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)); - rootPersister.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; } } @@ -2767,7 +2783,6 @@ private object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootL IType[] types = PropertyTypes; object[] values = new object[length]; bool[] laziness = PropertyLaziness; - string[] propSubclassNames = SubclassPropertySubclassNameClosure; for (int j = 0; j < length; j++) { @@ -2779,8 +2794,7 @@ private object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootL else if (allProperties || !laziness[i] || fetchedLazyProperties?.Contains(propNames[i]) == true) { //decide which ResultSet to get the property value from: - bool propertyIsDeferred = hasDeferred - && rootPersister.IsSubclassPropertyDeferred(propNames[i], propSubclassNames[i]); + var propertyIsDeferred = sequentialSql != null && IsPropertyDeferred(i); if (propertyIsDeferred && sequentialSelectEmpty) { values[j] = null; @@ -2798,15 +2812,11 @@ private object[] Hydrate(DbDataReader rs, object id, object obj, ILoadable rootL } } - if (sequentialResultSet != null) - { - sequentialResultSet.Close(); - } - return values; } finally { + sequentialResultSet?.Close(); if (sequentialSelect != null) { session.Batcher.CloseCommand(sequentialSelect, sequentialResultSet); @@ -2824,11 +2834,18 @@ protected bool UseGetGeneratedKeys() return Factory.Settings.IsGetGeneratedKeysEnabled; } + //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) { throw new NotSupportedException("no sequential selects"); } + protected virtual SqlString GetSequentialSelect() + { + return null; + } + /// /// Perform an SQL INSERT, and then retrieve a generated identifier. /// @@ -4613,5 +4630,48 @@ public string GetInfoString() return MessageHelper.InfoString(this); } #endregion + + internal SqlString GenerateSequentialSelect(AbstractEntityPersister subclassPersister) + { + //figure out which tables need to be fetched (only those that contains at least a no-lazy-property) + var tableNumbers = new HashSet(); + var props = subclassPersister.PropertyNames; + var classes = subclassPersister.PropertySubclassNames; + for (var i = 0; i < props.Length; i++) + { + var 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) + var columnNumbers = new List(); + var columnTableNumbers = SubclassColumnTableNumberClosure; + for (var i = 0; i < SubclassColumnClosure.Length; i++) + { + if (tableNumbers.Contains(columnTableNumbers[i])) + { + columnNumbers.Add(i); + } + } + + //figure out which formulas are needed (excludes lazy-properties) + var formulaNumbers = new List(); + var formulaTableNumbers = SubclassFormulaTableNumberClosure; + for (var 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 8991745ab38..992463e0e33 100644 --- a/src/NHibernate/Persister/Entity/ILoadable.cs +++ b/src/NHibernate/Persister/Entity/ILoadable.cs @@ -83,14 +83,18 @@ public static partial class LoadableExtensions /// //6.0 TODO: Merge into ILoadable public static object[] Hydrate( - this ILoadable loadable, DbDataReader rs, object id, object obj, ILoadable rootLoadable, + this ILoadable loadable, DbDataReader rs, object id, object obj, string[][] suffixedPropertyColumns, ISet fetchedLazyProperties, bool allProperties, ISessionImplementor session) { if (loadable is AbstractEntityPersister abstractEntityPersister) { return abstractEntityPersister.Hydrate( - rs, id, obj, rootLoadable, suffixedPropertyColumns, fetchedLazyProperties, allProperties, session); + rs, id, obj, suffixedPropertyColumns, fetchedLazyProperties, allProperties, session); } + + var rootLoadable = loadable.RootEntityName == loadable.EntityName + ? loadable + : (ILoadable) loadable.Factory.GetEntityPersister(loadable.RootEntityName); #pragma warning disable 618 // Fallback to the old behavior diff --git a/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs b/src/NHibernate/Persister/Entity/SingleTableEntityPersister.cs index 89a7423d174..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; @@ -66,9 +67,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(); @@ -165,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 +192,8 @@ public SingleTableEntityPersister(PersistentClass persistentClass, ICacheConcurr isInverses.Add(join.IsInverse); 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)); @@ -329,7 +331,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,79 +691,44 @@ protected override void AddDiscriminatorToInsert(SqlInsertBuilder insert) insert.AddColumn(DiscriminatorColumnName, DiscriminatorSQLValue); } + //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) { return - hasSequentialSelects && IsSubclassTableSequentialSelect(GetSubclassPropertyTableNumber(propertyName, entityName)); + hasSequentialSelects && IsSubclassTableSequentialSelect(base.GetSubclassPropertyTableNumber(propertyName, entityName)); } + protected override bool IsPropertyDeferred(int 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; } } - public int GetSubclassPropertyTableNumber(string propertyName, string entityName) + //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) { - 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 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) { - 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 +786,10 @@ public override string GetPropertyTableName(string propertyName) public override void PostInstantiate() { base.PostInstantiate(); - if (hasSequentialSelects) + if (_hasSequentialSelect && !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); } } }