/// <summary> /// Performs all the actual work needed to save an entity (well to get the save moved to /// the execution queue). /// </summary> /// <param name="entity">The entity to be saved </param> /// <param name="key">The id to be used for saving the entity (or null, in the case of identity columns) </param> /// <param name="persister">The entity's persister instance. </param> /// <param name="useIdentityColumn">Should an identity column be used for id generation? </param> /// <param name="anything">Generally cascade-specific information. </param> /// <param name="source">The session which is the source of the current event. </param> /// <param name="requiresImmediateIdAccess"> /// Is access to the identifier required immediately /// after the completion of the save? persist(), for example, does not require this... /// </param> /// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param> /// <returns> /// The id used to save the entity; may be null depending on the /// type of id generator used and the requiresImmediateIdAccess value /// </returns> protected virtual async Task <object> PerformSaveOrReplicateAsync(object entity, EntityKey key, IEntityPersister persister, bool useIdentityColumn, object anything, IEventSource source, bool requiresImmediateIdAccess, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); Validate(entity, persister, source); object id = key == null ? null : key.Identifier; // NH Different behavior (shouldDelayIdentityInserts=false anyway) //bool inTxn = source.ConnectionManager.IsInActiveTransaction; //bool shouldDelayIdentityInserts = !inTxn && !requiresImmediateIdAccess; bool shouldDelayIdentityInserts = false; // Put a placeholder in entries, so we don't recurse back and try to save() the // same object again. QUESTION: should this be done before onSave() is called? // likewise, should it be done before onUpdate()? source.PersistenceContext.AddEntry(entity, Status.Saving, null, null, id, null, LockMode.Write, useIdentityColumn, persister, false, false); await(CascadeBeforeSaveAsync(source, persister, entity, anything, cancellationToken)).ConfigureAwait(false); // NH-962: This was originally done before many-to-one cascades. if (useIdentityColumn && !shouldDelayIdentityInserts) { log.Debug("executing insertions"); await(source.ActionQueue.ExecuteInsertsAsync(cancellationToken)).ConfigureAwait(false); } object[] values = persister.GetPropertyValuesToInsert(entity, GetMergeMap(anything), source); IType[] types = persister.PropertyTypes; bool substitute = await(SubstituteValuesIfNecessaryAsync(entity, id, values, persister, source, cancellationToken)).ConfigureAwait(false); if (persister.HasCollections) { substitute = substitute || await(VisitCollectionsBeforeSaveAsync(entity, id, values, types, source, cancellationToken)).ConfigureAwait(false); } if (substitute) { persister.SetPropertyValues(entity, values); } TypeHelper.DeepCopy(values, types, persister.PropertyUpdateability, values, source); await(new ForeignKeys.Nullifier(entity, false, useIdentityColumn, source).NullifyTransientReferencesAsync(values, types, cancellationToken)).ConfigureAwait(false); new Nullability(source).CheckNullability(values, persister, false); if (useIdentityColumn) { EntityIdentityInsertAction insert = new EntityIdentityInsertAction(values, entity, persister, source, shouldDelayIdentityInserts); if (!shouldDelayIdentityInserts) { log.Debug("executing identity-insert immediately"); await(source.ActionQueue.ExecuteAsync(insert, cancellationToken)).ConfigureAwait(false); id = insert.GeneratedId; //now done in EntityIdentityInsertAction //persister.setIdentifier( entity, id, source.getEntityMode() ); key = source.GenerateEntityKey(id, persister); source.PersistenceContext.CheckUniqueness(key, entity); //source.getBatcher().executeBatch(); //found another way to ensure that all batched joined inserts have been executed } else { log.Debug("delaying identity-insert due to no transaction in progress"); source.ActionQueue.AddAction(insert); key = insert.DelayedEntityKey; } } object version = Versioning.GetVersion(values, persister); source.PersistenceContext.AddEntity( entity, persister.IsMutable ? Status.Loaded : Status.ReadOnly, values, key, version, LockMode.Write, useIdentityColumn, persister, VersionIncrementDisabled, false); //source.getPersistenceContext().removeNonExist( new EntityKey( id, persister, source.getEntityMode() ) ); if (!useIdentityColumn) { source.ActionQueue.AddAction(new EntityInsertAction(id, values, entity, version, persister, source)); } await(CascadeAfterSaveAsync(source, persister, entity, anything, cancellationToken)).ConfigureAwait(false); MarkInterceptorDirty(entity, persister, source); return(id); }
/// <summary> /// Performs all the actual work needed to save an entity (well to get the save moved to /// the execution queue). /// </summary> /// <param name="entity">The entity to be saved </param> /// <param name="key">The id to be used for saving the entity (or null, in the case of identity columns) </param> /// <param name="persister">The entity's persister instance. </param> /// <param name="useIdentityColumn">Should an identity column be used for id generation? </param> /// <param name="anything">Generally cascade-specific information. </param> /// <param name="source">The session which is the source of the current event. </param> /// <param name="requiresImmediateIdAccess"> /// Is access to the identifier required immediately /// after the completion of the save? persist(), for example, does not require this... /// </param> /// <returns> /// The id used to save the entity; may be null depending on the /// type of id generator used and the requiresImmediateIdAccess value /// </returns> protected virtual object PerformSaveOrReplicate(object entity, EntityKey key, IEntityPersister persister, bool useIdentityColumn, object anything, IEventSource source, bool requiresImmediateIdAccess) { Validate(entity, persister, source); object id = key == null ? null : key.Identifier; // NH Different behavior (shouldDelayIdentityInserts=false anyway) //bool inTxn = source.ConnectionManager.IsInActiveTransaction; //bool shouldDelayIdentityInserts = !inTxn && !requiresImmediateIdAccess; bool shouldDelayIdentityInserts = false; // Put a placeholder in entries, so we don't recurse back and try to save() the // same object again. QUESTION: should this be done before onSave() is called? // likewise, should it be done before onUpdate()? source.PersistenceContext.AddEntry(entity, Status.Saving, null, null, id, null, LockMode.Write, useIdentityColumn, persister, false, false); CascadeBeforeSave(source, persister, entity, anything); // NH-962: This was originally done before many-to-one cascades. if (useIdentityColumn && !shouldDelayIdentityInserts) { log.Debug("executing insertions"); source.ActionQueue.ExecuteInserts(); } object[] values = persister.GetPropertyValuesToInsert(entity, GetMergeMap(anything), source); IType[] types = persister.PropertyTypes; bool substitute = SubstituteValuesIfNecessary(entity, id, values, persister, source); if (persister.HasCollections) { substitute = substitute || VisitCollectionsBeforeSave(entity, id, values, types, source); } if (substitute) { persister.SetPropertyValues(entity, values, source.EntityMode); } TypeHelper.DeepCopy(values, types, persister.PropertyUpdateability, values, source); new ForeignKeys.Nullifier(entity, false, useIdentityColumn, source).NullifyTransientReferences(values, types); new Nullability(source).CheckNullability(values, persister, false); if (useIdentityColumn) { EntityIdentityInsertAction insert = new EntityIdentityInsertAction(values, entity, persister, source, shouldDelayIdentityInserts); if (!shouldDelayIdentityInserts) { log.Debug("executing identity-insert immediately"); source.ActionQueue.Execute(insert); id = insert.GeneratedId; //now done in EntityIdentityInsertAction //persister.setIdentifier( entity, id, source.getEntityMode() ); key = source.GenerateEntityKey(id, persister); source.PersistenceContext.CheckUniqueness(key, entity); //source.getBatcher().executeBatch(); //found another way to ensure that all batched joined inserts have been executed } else { log.Debug("delaying identity-insert due to no transaction in progress"); source.ActionQueue.AddAction(insert); key = insert.DelayedEntityKey; } } object version = Versioning.GetVersion(values, persister); source.PersistenceContext.AddEntity( entity, persister.IsMutable ? Status.Loaded : Status.ReadOnly, values, key, version, LockMode.Write, useIdentityColumn, persister, VersionIncrementDisabled, false); //source.getPersistenceContext().removeNonExist( new EntityKey( id, persister, source.getEntityMode() ) ); if (!useIdentityColumn) { source.ActionQueue.AddAction(new EntityInsertAction(id, values, entity, version, persister, source)); } CascadeAfterSave(source, persister, entity, anything); MarkInterceptorDirty(entity, persister, source); return id; }