Skip to content

WIP Convert EntityKey to struct #1976

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

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build-common/NHibernate.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<NhAppTargetFrameworks Condition ="$(NhAppTargetFrameworks) == ''">net461;netcoreapp2.0</NhAppTargetFrameworks>
<NhLibTargetFrameworks Condition ="$(NhLibTargetFrameworks) == ''">net461;netcoreapp2.0;netstandard2.0</NhLibTargetFrameworks>
<LangVersion>7.3</LangVersion>

<Product>NHibernate</Product>
<Company>NHibernate.info</Company>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../../build-common/NHibernate.props" />
<PropertyGroup>
<LangVersion/>
<Description>The Visual Basic Unit Tests for NHibernate.</Description>
<TargetFrameworks>$(NhAppTargetFrameworks)</TargetFrameworks>
<IsTestProject>true</IsTestProject>
Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate/Action/EntityIdentityInsertAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public EntityIdentityInsertAction(object[] state, object instance, IEntityPersis
: base(null, state, instance, persister, session)
{
this.isDelayed = isDelayed;
delayedEntityKey = this.isDelayed ? GenerateDelayedEntityKey() : null;
delayedEntityKey = this.isDelayed ? GenerateDelayedEntityKey() : EntityKey.Empty;
}

public object GeneratedId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ protected virtual async Task<object> PerformSaveAsync(object entity, object id,
}
else
{
key = null;
key = EntityKey.Empty;
}

if (InvokeSaveLifecycle(entity, persister, source))
Expand Down Expand Up @@ -184,7 +184,7 @@ protected virtual async Task<object> PerformSaveOrReplicateAsync(object entity,
cancellationToken.ThrowIfCancellationRequested();
Validate(entity, persister, source);

object id = key == null ? null : key.Identifier;
object id = key.IsEmpty ? null : key.Identifier;

bool shouldDelayIdentityInserts = !requiresImmediateIdAccess;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public virtual async Task OnReplicateAsync(ReplicateEvent @event, CancellationTo
}

bool regenerate = persister.IsIdentifierAssignedByInsert; // prefer re-generation of identity!
EntityKey key = regenerate ? null : source.GenerateEntityKey(id, persister);
EntityKey key = regenerate ? EntityKey.Empty : source.GenerateEntityKey(id, persister);

await (PerformSaveOrReplicateAsync(entity, key, persister, regenerate, replicationMode, source, true, cancellationToken)).ConfigureAwait(false);
}
Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate/Async/Impl/MultiCriteriaImpl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ private async Task GetResultsFromDatabaseAsync(IList results, CancellationToken

object o =
await (loader.GetRowFromResultSetAsync(reader, session, queryParameters, loader.GetLockModes(queryParameters.LockModes),
null, hydratedObjects[i], keys, true,
EntityKey.Empty, hydratedObjects[i], keys, true,
(persister, data) => cacheBatcher.AddToBatch(persister, data), cancellationToken)).ConfigureAwait(false);
if (createSubselects[i])
{
Expand Down
10 changes: 5 additions & 5 deletions src/NHibernate/Async/Loader/Loader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ protected async Task<object> LoadSingleRowAsync(DbDataReader resultSet, ISession
try
{
result =
await (GetRowFromResultSetAsync(resultSet, session, queryParameters, GetLockModes(queryParameters.LockModes), null,
await (GetRowFromResultSetAsync(resultSet, session, queryParameters, GetLockModes(queryParameters.LockModes), EntityKey.Empty,
hydratedObjects, new EntityKey[entitySpan], returnProxies, (persister, data) => cacheBatcher.AddToBatch(persister, data), cancellationToken)).ConfigureAwait(false);
}
catch (OperationCanceledException) { throw; }
Expand Down Expand Up @@ -185,7 +185,7 @@ internal async Task<object> GetRowFromResultSetAsync(DbDataReader resultSet, ISe
{
object entity = row[i];
var key = keys[i];
if (entity == null && key != null && IsChildFetchEntity(i))
if (entity == null && key.IsNotEmpty && IsChildFetchEntity(i))
{
// The entity was missing in the session, fallback on internal load (which will just yield a
// proxy if the persister supports it).
Expand Down Expand Up @@ -564,7 +564,7 @@ private async Task<EntityKey> GetKeyFromResultSetAsync(int i, IEntityPersister p
}
}

return resultId == null ? null : session.GenerateEntityKey(resultId, persister);
return resultId == null ? EntityKey.Empty : session.GenerateEntityKey(resultId, persister);
}

/// <summary>
Expand Down Expand Up @@ -620,7 +620,7 @@ private async Task<object[]> GetRowAsync(DbDataReader rs, ILoadable[] persisters
object obj = null;
EntityKey key = keys[i];

if (key == null)
if (key.IsEmpty)
{
// do nothing
/* TODO NH-1001 : if (persisters[i]...EntityType) is an OneToMany or a ManyToOne and
Expand Down Expand Up @@ -756,7 +756,7 @@ private async Task<object> InstanceNotYetLoadedAsync(DbDataReader dr, int i, ILo

string instanceClass = await (GetInstanceClassAsync(dr, i, persister, key.Identifier, session, cancellationToken)).ConfigureAwait(false);

if (optionalObjectKey != null && key.Equals(optionalObjectKey))
if (optionalObjectKey.IsNotEmpty && key.Equals(optionalObjectKey))
{
// its the given optional object
obj = optionalObject;
Expand Down
10 changes: 5 additions & 5 deletions src/NHibernate/Engine/BatchFetchQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public void Clear()
/// <param name="key">The entity key for which to locate any defined subselect fetch.</param>
/// <returns>The fetch descriptor; may return null if no subselect fetch queued for
/// this entity key.</returns>
public SubselectFetch GetSubselect(EntityKey key)
public SubselectFetch GetSubselect(in EntityKey key)
{
SubselectFetch result;
subselectsByEntityKey.TryGetValue(key, out result);
Expand All @@ -73,7 +73,7 @@ public SubselectFetch GetSubselect(EntityKey key)
/// </summary>
/// <param name="key">The entity for which to register the subselect fetch.</param>
/// <param name="subquery">The fetch descriptor.</param>
public void AddSubselect(EntityKey key, SubselectFetch subquery)
public void AddSubselect(in EntityKey key, SubselectFetch subquery)
{
subselectsByEntityKey[key] = subquery;
}
Expand All @@ -84,7 +84,7 @@ public void AddSubselect(EntityKey key, SubselectFetch subquery)
/// call this after loading the entity, since we might still
/// need to load its collections)
/// </summary>
public void RemoveSubselect(EntityKey key)
public void RemoveSubselect(in EntityKey key)
{
subselectsByEntityKey.Remove(key);
}
Expand All @@ -111,7 +111,7 @@ public void ClearSubselects()
/// referenced entity to be included in a batch even though it is
/// already associated with the <see cref="ISession" />.
/// </remarks>
public void AddBatchLoadableEntityKey(EntityKey key)
public void AddBatchLoadableEntityKey(in EntityKey key)
{
if (key.IsBatchLoadable)
{
Expand All @@ -129,7 +129,7 @@ public void AddBatchLoadableEntityKey(EntityKey key)
/// need to batch fetch it anymore, remove it from the queue
/// if necessary
/// </summary>
public void RemoveBatchLoadableEntityKey(EntityKey key)
public void RemoveBatchLoadableEntityKey(in EntityKey key)
{
if (key.IsBatchLoadable)
{
Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate/Engine/EntityEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ public EntityKey EntityKey
{
get
{
if (cachedEntityKey == null)
if (cachedEntityKey.IsEmpty)
{
if (id == null)
throw new InvalidOperationException("cannot generate an EntityKey when id is null.");
Expand Down
35 changes: 23 additions & 12 deletions src/NHibernate/Engine/EntityKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@

namespace NHibernate.Engine
{
//TODO 6.0: Remove IDeserializationCallback interface
/// <summary>
/// A globally unique identifier of an instance, consisting of the user-visible identifier
/// and the identifier space (eg. tablename)
/// </summary>
[Serializable]
public sealed class EntityKey : IDeserializationCallback, ISerializable, IEquatable<EntityKey>
public readonly struct EntityKey : ISerializable, IEquatable<EntityKey>
{
public static EntityKey Empty { get; } = new EntityKey();

public bool IsEmpty => identifier == null;
public bool IsNotEmpty => !IsEmpty;

private readonly object identifier;
private readonly IEntityPersister _persister;
// hashcode may vary among processes, they cannot be stored and have to be re-computed after deserialization
Expand All @@ -30,8 +34,16 @@ public EntityKey(object id, IEntityPersister persister)
private EntityKey(SerializationInfo info, StreamingContext context)
{
identifier = info.GetValue(nameof(Identifier), typeof(object));
if (identifier == null)
{
_hashCode = 0;
_persister = null;
return;
}

var factory = (ISessionFactoryImplementor) info.GetValue(nameof(_persister.Factory), typeof(ISessionFactoryImplementor));
var entityName = (string) info.GetValue(nameof(EntityName), typeof(string));
var entityName = info.GetString(nameof(EntityName));

_persister = factory.GetEntityPersister(entityName);
_hashCode = GenerateHashCode(_persister, identifier);
}
Expand All @@ -54,10 +66,8 @@ public override bool Equals(object other)

public bool Equals(EntityKey other)
{
if (other == null)
{
return false;
}
if (other.IsEmpty)
return IsEmpty;

return
other.RootEntityName.Equals(RootEntityName)
Expand All @@ -82,19 +92,20 @@ private static int GenerateHashCode(IEntityPersister persister, object id)

public override string ToString()
{
return "EntityKey" + MessageHelper.InfoString(_persister, Identifier, _persister.Factory);
return IsEmpty
? Util.StringHelper.NullObject
: "EntityKey" + MessageHelper.InfoString(_persister, Identifier, _persister?.Factory);
}

[SecurityCritical]
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue(nameof(Identifier), identifier);
if (identifier == null)
return;

info.AddValue(nameof(_persister.Factory), _persister.Factory);
info.AddValue(nameof(EntityName), EntityName);
}

[Obsolete("IDeserializationCallback interface has no usages and will be removed in a future version")]
public void OnDeserialization(object sender)
{}
}
}
32 changes: 16 additions & 16 deletions src/NHibernate/Engine/IPersistenceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public partial interface IPersistenceContext
/// <item><description>an entry of NO_ROW here is interpreted as an exception</description></item>
/// </list>
/// </remarks>
object[] GetCachedDatabaseSnapshot(EntityKey key);
object[] GetCachedDatabaseSnapshot(in EntityKey key);

/// <summary>
/// Get the values of the natural id fields as known to the underlying
Expand All @@ -123,22 +123,22 @@ public partial interface IPersistenceContext
object[] GetNaturalIdSnapshot(object id, IEntityPersister persister);

/// <summary> Add a canonical mapping from entity key to entity instance</summary>
void AddEntity(EntityKey key, object entity);
void AddEntity(in EntityKey key, object entity);

/// <summary>
/// Get the entity instance associated with the given <tt>EntityKey</tt>
/// </summary>
object GetEntity(EntityKey key);
object GetEntity(in EntityKey key);

/// <summary> Is there an entity with the given key in the persistence context</summary>
bool ContainsEntity(EntityKey key);
bool ContainsEntity(in EntityKey key);

/// <summary>
/// Remove an entity from the session cache, also clear
/// up other state associated with the entity, all except
/// for the <tt>EntityEntry</tt>
/// </summary>
object RemoveEntity(EntityKey key);
object RemoveEntity(in EntityKey key);

/// <summary> Get an entity cached by unique key</summary>
object GetEntity(EntityUniqueKey euk);
Expand Down Expand Up @@ -216,9 +216,9 @@ EntityEntry AddEntry(object entity, Status status, object[] loadedState, object
/// Attempts to check whether the given key represents an entity already loaded within the
/// current session.
/// </summary>
/// <param name="obj">The entity reference against which to perform the uniqueness check.</param>
/// <param name="key">The entity key.</param>
void CheckUniqueness(EntityKey key, object obj);
/// <param name="obj">The entity reference against which to perform the uniqueness check.</param>
void CheckUniqueness(in EntityKey key, object obj);

/// <summary>
/// If the existing proxy is insufficiently "narrow" (derived), instantiate a new proxy
Expand All @@ -231,14 +231,14 @@ EntityEntry AddEntry(object entity, Status status, object[] loadedState, object
/// <param name="key">The internal cache key for the proxied entity. </param>
/// <param name="obj">(optional) the actual proxied entity instance. </param>
/// <returns> An appropriately narrowed instance. </returns>
object NarrowProxy(INHibernateProxy proxy, IEntityPersister persister, EntityKey key, object obj);
object NarrowProxy(INHibernateProxy proxy, IEntityPersister persister, in EntityKey key, object obj);

/// <summary>
/// Return the existing proxy associated with the given <tt>EntityKey</tt>, or the
/// third argument (the entity associated with the key) if no proxy exists. Init
/// the proxy to the target implementation, if necessary.
/// </summary>
object ProxyFor(IEntityPersister persister, EntityKey key, object impl);
object ProxyFor(IEntityPersister persister,in EntityKey key, object impl);

/// <summary>
/// Return the existing proxy associated with the given <tt>EntityKey</tt>, or the
Expand Down Expand Up @@ -326,13 +326,13 @@ EntityEntry AddEntry(object entity, Status status, object[] loadedState, object
CollectionEntry GetCollectionEntryOrNull(object collection);

/// <summary> Get an existing proxy by key</summary>
object GetProxy(EntityKey key);
object GetProxy(in EntityKey key);

/// <summary> Add a proxy to the session cache</summary>
void AddProxy(EntityKey key, INHibernateProxy proxy);
void AddProxy(in EntityKey key, INHibernateProxy proxy);

/// <summary> Remove a proxy from the session cache</summary>
object RemoveProxy(EntityKey key);
object RemoveProxy(in EntityKey key);

/// <summary> Called before cascading</summary>
int IncrementCascadeLevel();
Expand Down Expand Up @@ -360,10 +360,10 @@ EntityEntry AddEntry(object entity, Status status, object[] loadedState, object
/// <summary>
/// Record the fact that the association belonging to the keyed entity is null.
/// </summary>
void AddNullProperty(EntityKey ownerKey, string propertyName);
void AddNullProperty(in EntityKey ownerKey, string propertyName);

/// <summary> Is the association property belonging to the keyed entity null?</summary>
bool IsPropertyNull(EntityKey ownerKey, string propertyName);
bool IsPropertyNull(in EntityKey ownerKey, string propertyName);

/// <summary>
/// Change the read-only status of an entity (or proxy).
Expand Down Expand Up @@ -398,7 +398,7 @@ EntityEntry AddEntry(object entity, Status status, object[] loadedState, object
/// <seealso cref="IPersistenceContext.SetReadOnly(object, bool)" />
bool IsReadOnly(object entityOrProxy);

void ReplaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, object generatedId);
void ReplaceDelayedEntityIdentityInsertKeys(in EntityKey oldKey, object generatedId);

/// <summary>Is in a two-phase load? </summary>
bool IsLoadFinished { get; }
Expand All @@ -425,7 +425,7 @@ public static EntityEntry AddEntity(
object entity,
Status status,
object[] loadedState,
EntityKey entityKey,
in EntityKey entityKey,
object version,
LockMode lockMode,
bool existsInDatabase,
Expand Down
Loading