Skip to content

Refactor sequential select #1979

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jan 31, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/NHibernate/Async/Loader/Loader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
94 changes: 49 additions & 45 deletions src/NHibernate/Async/Persister/Entity/AbstractEntityPersister.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -339,7 +340,23 @@ protected async Task<int> DehydrateAsync(object id, object[] fields, object rowI
/// Unmarshall the fields of a persistent instance from a result set,
/// without resolving associations or collections
/// </summary>
public async Task<object[]> HydrateAsync(DbDataReader rs, object id, object obj, ILoadable rootLoadable,
//Since v5.3
[Obsolete("Use the overload without the rootLoadable parameter instead")]
public Task<object[]> HydrateAsync(DbDataReader rs, object id, object obj, ILoadable rootLoadable,
string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<object[]>(cancellationToken);
}
return HydrateAsync(rs, id, obj, suffixedPropertyColumns, allProperties, session, cancellationToken);
}

/// <summary>
/// Unmarshall the fields of a persistent instance from a result set,
/// without resolving associations or collections
/// </summary>
public async Task<object[]> HydrateAsync(DbDataReader rs, object id, object obj,
string[][] suffixedPropertyColumns, bool allProperties, ISessionImplementor session, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
Expand All @@ -348,57 +365,49 @@ public async Task<object[]> 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 <join> 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 <join/> 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 <join/>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 <join> 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 <join/> 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 <join/>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;
}
}

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++)
{
Expand All @@ -409,8 +418,7 @@ public async Task<object[]> 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 = sequentialSql != null && IsPropertyDeferred(i);
if (propertyIsDeferred && sequentialSelectEmpty)
{
values[i] = null;
Expand All @@ -428,15 +436,11 @@ public async Task<object[]> 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);
Expand Down
40 changes: 40 additions & 0 deletions src/NHibernate/Async/Persister/Entity/ILoadable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//------------------------------------------------------------------------------


using System;
using NHibernate.Type;
using NHibernate.Engine;
using System.Data.Common;
Expand All @@ -23,7 +24,46 @@ public partial interface ILoadable : IEntityPersister
/// <summary>
/// Retrieve property values from one row of a result set
/// </summary>
//Since v5.3
[Obsolete("Use the extension method without the rootLoadable parameter instead")]
Task<object[]> 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<object[]> HydrateAsync(
this ILoadable loadable,
DbDataReader rs,
object id,
object obj,
string[][] suffixedPropertyColumns,
bool allProperties,
ISessionImplementor session, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<object[]>(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<object[]>(ex);
}
}
}
}
2 changes: 1 addition & 1 deletion src/NHibernate/Loader/Loader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Loading