/// <summary> /// Prepares the save call using the given requested id. /// </summary> /// <param name="entity">The entity to be saved. </param> /// <param name="requestedId">The id to which to associate the entity. </param> /// <param name="entityName">The name of the entity being saved. </param> /// <param name="anything">Generally cascade-specific information. </param> /// <param name="source">The session which is the source of this save event. </param> /// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param> /// <returns> The id used to save the entity. </returns> protected virtual Task <object> SaveWithRequestedIdAsync(object entity, object requestedId, string entityName, object anything, IEventSource source, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(Task.FromCanceled <object>(cancellationToken)); } try { return(PerformSaveAsync(entity, requestedId, source.GetEntityPersister(entityName, entity), false, anything, source, true, cancellationToken)); } catch (Exception ex) { return(Task.FromException <object>(ex)); } }
protected virtual void EntityIsPersistent(MergeEvent @event, IDictionary copyCache) { log.Debug("ignoring persistent instance"); //TODO: check that entry.getIdentifier().equals(requestedId) object entity = @event.Entity; IEventSource source = @event.Session; IEntityPersister persister = source.GetEntityPersister(@event.EntityName, entity); copyCache[entity] = entity; //before cascade! CascadeOnMerge(source, persister, entity, copyCache); CopyValues(persister, entity, entity, source, copyCache); @event.Result = entity; }
protected virtual async Task EntityIsPersistentAsync(MergeEvent @event, IDictionary copyCache, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); log.Debug("ignoring persistent instance"); //TODO: check that entry.getIdentifier().equals(requestedId) object entity = @event.Entity; IEventSource source = @event.Session; IEntityPersister persister = source.GetEntityPersister(@event.EntityName, entity); ((EventCache)copyCache).Add(entity, entity, true); //before cascade! await(CascadeOnMergeAsync(source, persister, entity, copyCache, cancellationToken)).ConfigureAwait(false); await(CopyValuesAsync(persister, entity, entity, source, copyCache, cancellationToken)).ConfigureAwait(false); @event.Result = entity; }
protected virtual void EntityIsTransient(MergeEvent @event, IDictionary copyCache) { log.Debug("merging transient instance"); object entity = @event.Entity; IEventSource source = @event.Session; IEntityPersister persister = source.GetEntityPersister(@event.EntityName, entity); string entityName = persister.EntityName; object id = persister.HasIdentifierProperty ? persister.GetIdentifier(entity, source.EntityMode) : null; object copy = persister.Instantiate(id, source.EntityMode); // should this be Session.instantiate(Persister, ...)? copyCache[entity] = copy; //before cascade! // cascade first, so that all unsaved objects get their // copy created before we actually copy //cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE); base.CascadeBeforeSave(source, persister, entity, copyCache); CopyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.ForeignKeyFromParent); //this bit is only *really* absolutely necessary for handling //requestedId, but is also good if we merge multiple object //graphs, since it helps ensure uniqueness object requestedId = @event.RequestedId; if (requestedId == null) { SaveWithGeneratedId(copy, entityName, copyCache, source, false); } else { SaveWithRequestedId(copy, requestedId, entityName, copyCache, source); } // cascade first, so that all unsaved objects get their // copy created before we actually copy base.CascadeAfterSave(source, persister, entity, copyCache); CopyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.ForeignKeyToParent); @event.Result = copy; }
protected virtual void EntityIsPersistent(PersistEvent @event, IDictionary createCache) { log.Debug("ignoring persistent instance"); IEventSource source = @event.Session; //TODO: check that entry.getIdentifier().equals(requestedId) object entity = source.PersistenceContext.Unproxy(@event.Entity); IEntityPersister persister = source.GetEntityPersister(@event.EntityName, entity); object tempObject; tempObject = createCache[entity]; createCache[entity] = entity; if (tempObject == null) { //TODO: merge into one method! CascadeBeforeSave(source, persister, entity, createCache); CascadeAfterSave(source, persister, entity, createCache); } }
protected object GetValue(Property property, object[] state) { var index = System.Array.IndexOf(Persister.PropertyNames, property.Name); if (index < 0) { return(null); } var value = state[index]; // For entities, return the entity id. if (value != null && property.Type is EntityType entityType) { var entityName = entityType.GetAssociatedEntityName(); var persister = Session.GetEntityPersister(entityName, value); var id = persister.GetIdentifier(value); value = id; } return(value); }
/// <summary> /// Prepares the save call using the given requested id. /// </summary> /// <param name="entity">The entity to be saved. </param> /// <param name="requestedId">The id to which to associate the entity. </param> /// <param name="entityName">The name of the entity being saved. </param> /// <param name="anything">Generally cascade-specific information. </param> /// <param name="source">The session which is the source of this save event. </param> /// <returns> The id used to save the entity. </returns> protected virtual object SaveWithRequestedId(object entity, object requestedId, string entityName, object anything, IEventSource source) { return PerformSave(entity, requestedId, source.GetEntityPersister(entityName, entity), false, anything, source, true); }
/// <summary> /// Prepares the save call using a newly generated id. /// </summary> /// <param name="entity">The entity to be saved </param> /// <param name="entityName">The entity-name for the entity to be saved </param> /// <param name="anything">Generally cascade-specific information. </param> /// <param name="source">The session which is the source of this save event. </param> /// <param name="requiresImmediateIdAccess"> /// does the event context require /// access to the identifier immediately after execution of this method (if /// not, post-insert style id generators may be postponed if we are outside /// a transaction). /// </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 SaveWithGeneratedId(object entity, string entityName, object anything, IEventSource source, bool requiresImmediateIdAccess) { IEntityPersister persister = source.GetEntityPersister(entityName, entity); object generatedId = persister.IdentifierGenerator.Generate(source, entity); if (generatedId == null) { throw new IdentifierGenerationException("null id generated for:" + entity.GetType()); } else if (generatedId == IdentifierGeneratorFactory.ShortCircuitIndicator) { return source.GetIdentifier(entity); } else if (generatedId == IdentifierGeneratorFactory.PostInsertIndicator) { return PerformSave(entity, null, persister, true, anything, source, requiresImmediateIdAccess); } else { if (log.IsDebugEnabled) { log.Debug(string.Format("generated identifier: {0}, using strategy: {1}", persister.IdentifierType.ToLoggableString(generatedId, source.Factory), persister.IdentifierGenerator.GetType().FullName)); } return PerformSave(entity, generatedId, persister, false, anything, source, true); } }
public virtual async Task OnReplicateAsync(ReplicateEvent @event, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); IEventSource source = @event.Session; if (source.PersistenceContext.ReassociateIfUninitializedProxy(@event.Entity)) { log.Debug("uninitialized proxy passed to replicate()"); return; } object entity = await(source.PersistenceContext.UnproxyAndReassociateAsync(@event.Entity, cancellationToken)).ConfigureAwait(false); if (source.PersistenceContext.IsEntryFor(entity)) { log.Debug("ignoring persistent instance passed to replicate()"); //hum ... should we cascade anyway? throw an exception? fine like it is? return; } IEntityPersister persister = source.GetEntityPersister(@event.EntityName, entity); // get the id from the object /*if ( persister.isUnsaved(entity, source) ) { * throw new TransientObjectException("transient instance passed to replicate()"); * }*/ object id = persister.GetIdentifier(entity); if (id == null) { throw new TransientObjectException("instance with null id passed to replicate()"); } ReplicationMode replicationMode = @event.ReplicationMode; object oldVersion; if (replicationMode == ReplicationMode.Exception) { //always do an INSERT, and let it fail by constraint violation oldVersion = null; } else { //what is the version on the database? oldVersion = await(persister.GetCurrentVersionAsync(id, source, cancellationToken)).ConfigureAwait(false); } if (oldVersion != null) { if (log.IsDebugEnabled()) { log.Debug("found existing row for {0}", MessageHelper.InfoString(persister, id, source.Factory)); } // HHH-2378 object realOldVersion = persister.IsVersioned ? oldVersion : null; bool canReplicate = replicationMode.ShouldOverwriteCurrentVersion(entity, realOldVersion, persister.GetVersion(entity), persister.VersionType); if (canReplicate) { //will result in a SQL UPDATE: await(PerformReplicationAsync(entity, id, realOldVersion, persister, replicationMode, source, cancellationToken)).ConfigureAwait(false); } else { //else do nothing (don't even reassociate object!) log.Debug("no need to replicate"); } //TODO: would it be better to do a refresh from db? } else { // no existing row - do an insert if (log.IsDebugEnabled()) { log.Debug("no existing row, replicating new instance {0}", MessageHelper.InfoString(persister, id, source.Factory)); } bool regenerate = persister.IsIdentifierAssignedByInsert; // prefer re-generation of identity! EntityKey key = regenerate ? null : source.GenerateEntityKey(id, persister); await(PerformSaveOrReplicateAsync(entity, key, persister, regenerate, replicationMode, source, true, cancellationToken)).ConfigureAwait(false); } }
public virtual void OnDelete(DeleteEvent @event, ISet <object> transientEntities) { IEventSource source = @event.Session; IPersistenceContext persistenceContext = source.PersistenceContext; object entity = persistenceContext.UnproxyAndReassociate(@event.Entity); EntityEntry entityEntry = persistenceContext.GetEntry(entity); IEntityPersister persister; object id; object version; if (entityEntry == null) { log.Debug("entity was not persistent in delete processing"); persister = source.GetEntityPersister(@event.EntityName, entity); if (ForeignKeys.IsTransientSlow(persister.EntityName, entity, source)) { DeleteTransientEntity(source, entity, @event.CascadeDeleteEnabled, persister, transientEntities); // EARLY EXIT!!! return; } else { PerformDetachedEntityDeletionCheck(@event); } id = persister.GetIdentifier(entity); if (id == null) { throw new TransientObjectException("the detached instance passed to delete() had a null identifier"); } EntityKey key = source.GenerateEntityKey(id, persister); persistenceContext.CheckUniqueness(key, entity); new OnUpdateVisitor(source, id, entity).Process(entity, persister); version = persister.GetVersion(entity); entityEntry = persistenceContext.AddEntity( entity, persister.IsMutable ? Status.Loaded : Status.ReadOnly, persister.GetPropertyValues(entity), key, version, LockMode.None, true, persister, false); } else { log.Debug("deleting a persistent instance"); if (entityEntry.Status == Status.Deleted || entityEntry.Status == Status.Gone) { log.Debug("object was already deleted"); return; } persister = entityEntry.Persister; id = entityEntry.Id; version = entityEntry.Version; } if (InvokeDeleteLifecycle(source, entity, persister)) { return; } DeleteEntity(source, entity, entityEntry, @event.CascadeDeleteEnabled, persister, transientEntities); if (source.Factory.Settings.IsIdentifierRollbackEnabled) { persister.ResetIdentifier(entity, id, version); } }
public virtual async Task OnMergeAsync(MergeEvent @event, IDictionary copiedAlready, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); EventCache copyCache = (EventCache)copiedAlready; IEventSource source = @event.Session; object original = @event.Original; if (original != null) { object entity; if (original.IsProxy()) { ILazyInitializer li = ((INHibernateProxy)original).HibernateLazyInitializer; if (li.IsUninitialized) { log.Debug("ignoring uninitialized proxy"); @event.Result = await(source.LoadAsync(li.EntityName, li.Identifier, cancellationToken)).ConfigureAwait(false); return; //EARLY EXIT! } else { entity = await(li.GetImplementationAsync(cancellationToken)).ConfigureAwait(false); } } else { entity = original; } if (copyCache.Contains(entity) && copyCache.IsOperatedOn(entity)) { log.Debug("already in merge process"); @event.Result = entity; } else { if (copyCache.Contains(entity)) { log.Info("already in copyCache; setting in merge process"); copyCache.SetOperatedOn(entity, true); } @event.Entity = entity; EntityState entityState = EntityState.Undefined; if (ReferenceEquals(null, @event.EntityName)) { @event.EntityName = source.BestGuessEntityName(entity); } // Check the persistence context for an entry relating to this // entity to be merged... EntityEntry entry = source.PersistenceContext.GetEntry(entity); if (entry == null) { IEntityPersister persister = source.GetEntityPersister(@event.EntityName, entity); object id = persister.GetIdentifier(entity); if (id != null) { EntityKey key = source.GenerateEntityKey(id, persister); object managedEntity = source.PersistenceContext.GetEntity(key); entry = source.PersistenceContext.GetEntry(managedEntity); if (entry != null) { // we have specialized case of a detached entity from the // perspective of the merge operation. Specifically, we // have an incoming entity instance which has a corresponding // entry in the current persistence context, but registered // under a different entity instance entityState = EntityState.Detached; } } } if (entityState == EntityState.Undefined) { entityState = await(GetEntityStateAsync(entity, @event.EntityName, entry, source, cancellationToken)).ConfigureAwait(false); } switch (entityState) { case EntityState.Persistent: await(EntityIsPersistentAsync(@event, copyCache, cancellationToken)).ConfigureAwait(false); break; case EntityState.Transient: await(EntityIsTransientAsync(@event, copyCache, cancellationToken)).ConfigureAwait(false); break; case EntityState.Detached: await(EntityIsDetachedAsync(@event, copyCache, cancellationToken)).ConfigureAwait(false); break; default: throw new ObjectDeletedException("deleted instance passed to merge", null, GetLoggableName(@event.EntityName, entity)); } } } }
protected virtual async Task EntityIsDetachedAsync(MergeEvent @event, IDictionary copyCache, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); log.Debug("merging detached instance"); object entity = @event.Entity; IEventSource source = @event.Session; IEntityPersister persister = source.GetEntityPersister(@event.EntityName, entity); string entityName = persister.EntityName; object id = @event.RequestedId; if (id == null) { id = persister.GetIdentifier(entity); } else { // check that entity id = requestedId object entityId = persister.GetIdentifier(entity); if (!persister.IdentifierType.IsEqual(id, entityId, source.Factory)) { throw new HibernateException("merge requested with id not matching id of passed entity"); } } string previousFetchProfile = source.FetchProfile; source.FetchProfile = "merge"; //we must clone embedded composite identifiers, or //we will get back the same instance that we pass in object clonedIdentifier = persister.IdentifierType.DeepCopy(id, source.Factory); object result = await(source.GetAsync(persister.EntityName, clonedIdentifier, cancellationToken)).ConfigureAwait(false); source.FetchProfile = previousFetchProfile; if (result == null) { //TODO: we should throw an exception if we really *know* for sure // that this is a detached instance, rather than just assuming //throw new StaleObjectStateException(entityName, id); // we got here because we assumed that an instance // with an assigned id was detached, when it was // really persistent await(EntityIsTransientAsync(@event, copyCache, cancellationToken)).ConfigureAwait(false); } else { // NH different behavior : NH-1517 if (InvokeUpdateLifecycle(entity, persister, source)) { return; } ((EventCache)copyCache).Add(entity, result, true); //before cascade! object target = source.PersistenceContext.Unproxy(result); if (target == entity) { throw new AssertionFailure("entity was not detached"); } else if (!(await(source.GetEntityNameAsync(target, cancellationToken)).ConfigureAwait(false)).Equals(entityName)) { throw new WrongClassException("class of the given object did not match class of persistent copy", @event.RequestedId, persister.EntityName); } else if (IsVersionChanged(entity, source, persister, target)) { if (source.Factory.Statistics.IsStatisticsEnabled) { source.Factory.StatisticsImplementor.OptimisticFailure(entityName); } throw new StaleObjectStateException(persister.EntityName, id); } // cascade first, so that all unsaved objects get their // copy created before we actually copy await(CascadeOnMergeAsync(source, persister, entity, copyCache, cancellationToken)).ConfigureAwait(false); await(CopyValuesAsync(persister, entity, target, source, copyCache, cancellationToken)).ConfigureAwait(false); //copyValues works by reflection, so explicitly mark the entity instance dirty MarkInterceptorDirty(entity, target); @event.Result = result; } }
private object MergeTransientEntity(object entity, string entityName, object requestedId, IEventSource source, IDictionary copyCache) { IEntityPersister persister = source.GetEntityPersister(entityName, entity); object id = persister.HasIdentifierProperty ? persister.GetIdentifier(entity, source.EntityMode) : null; object copy = null; if (copyCache.Contains(entity)) { copy = copyCache[entity]; persister.SetIdentifier(copy, id, source.EntityMode); } else { copy = source.Instantiate(persister, id); ((EventCache)copyCache).Add(entity, copy, true); // before cascade! } // cascade first, so that all unsaved objects get their // copy created before we actually copy //cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE); base.CascadeBeforeSave(source, persister, entity, copyCache); CopyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.ForeignKeyFromParent); try { // try saving; check for non-nullable properties that are null or transient entities before saving this.SaveTransientEntity(copy, entityName, requestedId, source, copyCache); } catch (PropertyValueException ex) { string propertyName = ex.PropertyName; object propertyFromCopy = persister.GetPropertyValue(copy, propertyName, source.EntityMode); object propertyFromEntity = persister.GetPropertyValue(entity, propertyName, source.EntityMode); IType propertyType = persister.GetPropertyType(propertyName); EntityEntry copyEntry = source.PersistenceContext.GetEntry(copy); if (propertyFromCopy == null || !propertyType.IsEntityType) { log.InfoFormat("property '{0}.{1}' is null or not an entity; {1} =[{2}]", copyEntry.EntityName, propertyName, propertyFromCopy); throw; } if (!copyCache.Contains(propertyFromEntity)) { log.InfoFormat("property '{0}.{1}' from original entity is not in copyCache; {1} =[{2}]", copyEntry.EntityName, propertyName, propertyFromEntity); throw; } if (((EventCache)copyCache).IsOperatedOn(propertyFromEntity)) { log.InfoFormat("property '{0}.{1}' from original entity is in copyCache and is in the process of being merged; {1} =[{2}]", copyEntry.EntityName, propertyName, propertyFromEntity); } else { log.InfoFormat("property '{0}.{1}' from original entity is in copyCache and is not in the process of being merged; {1} =[{2}]", copyEntry.EntityName, propertyName, propertyFromEntity); } // continue...; we'll find out if it ends up not getting saved later } // cascade first, so that all unsaved objects get their // copy created before we actually copy base.CascadeAfterSave(source, persister, entity, copyCache); CopyValues(persister, entity, copy, source, copyCache, ForeignKeyDirection.ForeignKeyToParent); return copy; }
public virtual void OnRefresh(RefreshEvent @event, IDictionary refreshedAlready) { IEventSource source = @event.Session; bool isTransient = !source.Contains(@event.Entity); if (source.PersistenceContext.ReassociateIfUninitializedProxy(@event.Entity)) { if (isTransient) { source.SetReadOnly(@event.Entity, source.DefaultReadOnly); } return; } object obj = source.PersistenceContext.UnproxyAndReassociate(@event.Entity); if (refreshedAlready.Contains(obj)) { log.Debug("already refreshed"); return; } EntityEntry e = source.PersistenceContext.GetEntry(obj); IEntityPersister persister; object id; if (e == null) { persister = source.GetEntityPersister(null, obj); //refresh() does not pass an entityName id = persister.GetIdentifier(obj); if (log.IsDebugEnabled()) { log.Debug("refreshing transient {0}", MessageHelper.InfoString(persister, id, source.Factory)); } EntityKey key = source.GenerateEntityKey(id, persister); if (source.PersistenceContext.GetEntry(key) != null) { throw new PersistentObjectException("attempted to refresh transient instance when persistent instance was already associated with the Session: " + MessageHelper.InfoString(persister, id, source.Factory)); } } else { if (log.IsDebugEnabled()) { log.Debug("refreshing {0}", MessageHelper.InfoString(e.Persister, e.Id, source.Factory)); } if (!e.ExistsInDatabase) { throw new HibernateException("this instance does not yet exist as a row in the database"); } persister = e.Persister; id = e.Id; } // cascade the refresh prior to refreshing this entity refreshedAlready[obj] = obj; new Cascade(CascadingAction.Refresh, CascadePoint.BeforeRefresh, source).CascadeOn(persister, obj, refreshedAlready); if (e != null) { EntityKey key = source.GenerateEntityKey(id, persister); source.PersistenceContext.RemoveEntity(key); if (persister.HasCollections) { new EvictVisitor(source).Process(obj, persister); } } if (persister.HasCache) { CacheKey ck = source.GenerateCacheKey(id, persister.IdentifierType, persister.RootEntityName); persister.Cache.Remove(ck); } EvictCachedCollections(persister, id, source.Factory); // NH Different behavior : NH-1601 // At this point the entity need the real refresh, all elementes of collections are Refreshed, // the collection state was evicted, but the PersistentCollection (in the entity state) // is associated with a possible previous session. new WrapVisitor(source).Process(obj, persister); string previousFetchProfile = source.FetchProfile; source.FetchProfile = "refresh"; object result = persister.Load(id, obj, @event.LockMode, source); if (result != null) { if (!persister.IsMutable) { source.SetReadOnly(result, true); } else { source.SetReadOnly(result, e == null ? source.DefaultReadOnly : !e.IsModifiableEntity()); } } source.FetchProfile = previousFetchProfile; // NH Different behavior : we are ignoring transient entities without throw any kind of exception // because a transient entity is "self refreshed" if (!ForeignKeys.IsTransientFast(persister.EntityName, obj, @event.Session).GetValueOrDefault(result == null)) { UnresolvableObjectException.ThrowIfNull(result, id, persister.EntityName); } }
public virtual void OnMerge(MergeEvent @event, IDictionary copyCache) { IEventSource source = @event.Session; object original = @event.Original; if (original != null) { object entity; if (original is INHibernateProxy) { ILazyInitializer li = ((INHibernateProxy)original).HibernateLazyInitializer; if (li.IsUninitialized) { log.Debug("ignoring uninitialized proxy"); @event.Result = source.Load(li.PersistentClass, li.Identifier); return; //EARLY EXIT! } else { entity = li.GetImplementation(); } } else { entity = original; } if (copyCache.Contains(entity)) { log.Debug("already merged"); @event.Result = entity; } else { @event.Entity = entity; EntityState entityState = EntityState.Undefined; // Check the persistence context for an entry relating to this // entity to be merged... EntityEntry entry = source.PersistenceContext.GetEntry(entity); if (entry == null) { IEntityPersister persister = source.GetEntityPersister(@event.EntityName, entity); object id = persister.GetIdentifier(entity, source.EntityMode); if (id != null) { EntityKey key = new EntityKey(id, persister, source.EntityMode); object managedEntity = source.PersistenceContext.GetEntity(key); entry = source.PersistenceContext.GetEntry(managedEntity); if (entry != null) { // we have specialized case of a detached entity from the // perspective of the merge operation. Specifically, we // have an incoming entity instance which has a corresponding // entry in the current persistence context, but registered // under a different entity instance entityState = EntityState.Detached; } } } if (entityState == EntityState.Undefined) { entityState = GetEntityState(entity, @event.EntityName, entry, source); } switch (entityState) { case EntityState.Persistent: EntityIsPersistent(@event, copyCache); break; case EntityState.Transient: EntityIsTransient(@event, copyCache); break; case EntityState.Detached: EntityIsDetached(@event, copyCache); break; default: throw new ObjectDeletedException("deleted instance passed to merge", null, GetLoggableName(@event.EntityName, entity)); } } } }
/// <summary> /// Prepares the save call using the given requested id. /// </summary> /// <param name="entity">The entity to be saved. </param> /// <param name="requestedId">The id to which to associate the entity. </param> /// <param name="entityName">The name of the entity being saved. </param> /// <param name="anything">Generally cascade-specific information. </param> /// <param name="source">The session which is the source of this save event. </param> /// <returns> The id used to save the entity. </returns> protected virtual object SaveWithRequestedId(object entity, object requestedId, string entityName, object anything, IEventSource source) { return(PerformSave(entity, requestedId, source.GetEntityPersister(entityName, entity), false, anything, source, true)); }
private async Task <object> MergeTransientEntityAsync(object entity, string entityName, object requestedId, IEventSource source, IDictionary copyCache, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); IEntityPersister persister = source.GetEntityPersister(entityName, entity); object id = persister.HasIdentifierProperty ? persister.GetIdentifier(entity) : null; object copy = null; if (copyCache.Contains(entity)) { copy = copyCache[entity]; persister.SetIdentifier(copy, id); } else { copy = source.Instantiate(persister, id); ((EventCache)copyCache).Add(entity, copy, true); // before cascade! } // cascade first, so that all unsaved objects get their // copy created before we actually copy //cascadeOnMerge(event, persister, entity, copyCache, Cascades.CASCADE_BEFORE_MERGE); await(base.CascadeBeforeSaveAsync(source, persister, entity, copyCache, cancellationToken)).ConfigureAwait(false); await(CopyValuesAsync(persister, entity, copy, source, copyCache, ForeignKeyDirection.ForeignKeyFromParent, cancellationToken)).ConfigureAwait(false); try { // try saving; check for non-nullable properties that are null or transient entities before saving await(this.SaveTransientEntityAsync(copy, entityName, requestedId, source, copyCache, cancellationToken)).ConfigureAwait(false); } catch (PropertyValueException ex) { string propertyName = ex.PropertyName; object propertyFromCopy = persister.GetPropertyValue(copy, propertyName); object propertyFromEntity = persister.GetPropertyValue(entity, propertyName); IType propertyType = persister.GetPropertyType(propertyName); EntityEntry copyEntry = source.PersistenceContext.GetEntry(copy); if (propertyFromCopy == null || !propertyType.IsEntityType) { log.Info("property '{0}.{1}' is null or not an entity; {1} =[{2}]", copyEntry.EntityName, propertyName, propertyFromCopy); throw; } if (!copyCache.Contains(propertyFromEntity)) { log.Info("property '{0}.{1}' from original entity is not in copyCache; {1} =[{2}]", copyEntry.EntityName, propertyName, propertyFromEntity); throw; } if (((EventCache)copyCache).IsOperatedOn(propertyFromEntity)) { log.Info(ex, "property '{0}.{1}' from original entity is in copyCache and is in the process of being merged; {1} =[{2}]", copyEntry.EntityName, propertyName, propertyFromEntity); } else { log.Info(ex, "property '{0}.{1}' from original entity is in copyCache and is not in the process of being merged; {1} =[{2}]", copyEntry.EntityName, propertyName, propertyFromEntity); } // continue...; we'll find out if it ends up not getting saved later } // cascade first, so that all unsaved objects get their // copy created before we actually copy await(base.CascadeAfterSaveAsync(source, persister, entity, copyCache, cancellationToken)).ConfigureAwait(false); await(CopyValuesAsync(persister, entity, copy, source, copyCache, ForeignKeyDirection.ForeignKeyToParent, cancellationToken)).ConfigureAwait(false); return(copy); }
public virtual void OnRefresh(RefreshEvent @event, IDictionary refreshedAlready) { IEventSource source = @event.Session; if (source.PersistenceContext.ReassociateIfUninitializedProxy(@event.Entity)) { return; } object obj = source.PersistenceContext.UnproxyAndReassociate(@event.Entity); if (refreshedAlready.Contains(obj)) { log.Debug("already refreshed"); return; } EntityEntry e = source.PersistenceContext.GetEntry(obj); IEntityPersister persister; object id; if (e == null) { persister = source.GetEntityPersister(null, obj); //refresh() does not pass an entityName id = persister.GetIdentifier(obj, source.EntityMode); if (log.IsDebugEnabled) { log.Debug("refreshing transient " + MessageHelper.InfoString(persister, id, source.Factory)); } EntityKey key = new EntityKey(id, persister, source.EntityMode); if (source.PersistenceContext.GetEntry(key) != null) { throw new PersistentObjectException("attempted to refresh transient instance when persistent instance was already associated with the Session: " + MessageHelper.InfoString(persister, id, source.Factory)); } } else { if (log.IsDebugEnabled) { log.Debug("refreshing " + MessageHelper.InfoString(e.Persister, e.Id, source.Factory)); } if (!e.ExistsInDatabase) { throw new HibernateException("this instance does not yet exist as a row in the database"); } persister = e.Persister; id = e.Id; } // cascade the refresh prior to refreshing this entity refreshedAlready[obj] = obj; new Cascade(CascadingAction.Refresh, CascadePoint.BeforeRefresh, source).CascadeOn(persister, obj, refreshedAlready); if (e != null) { EntityKey key = new EntityKey(id, persister, source.EntityMode); source.PersistenceContext.RemoveEntity(key); if (persister.HasCollections) { new EvictVisitor(source).Process(obj, persister); } } if (persister.HasCache) { CacheKey ck = new CacheKey(id, persister.IdentifierType, persister.RootEntityName, source.EntityMode, source.Factory); persister.Cache.Remove(ck); } EvictCachedCollections(persister, id, source.Factory); // NH Different behavior : NH-1601 // At this point the entity need the real refresh, all elementes of collections are Refreshed, // the collection state was evicted, but the PersistentCollection (in the entity state) // is associated with a possible previous session. new WrapVisitor(source).Process(obj, persister); // NH-3253: Forcing simple load to prevent the redundante instance //on session: //Ocurre when: // -there is a 'one-to-many' 'Parent-Child' relationship; // -AND the 'Parent' 'many-to-one' association is ' fetch="select" '; // -AND 'Child' is using a 'composite-id' and 'key-many-to-one' to the //'Parent'. object result; if (this.IsReferencedByCompositeId(persister)) { result = persister.Load(id, obj, @event.LockMode, source); } string previousFetchProfile = source.FetchProfile; source.FetchProfile = "refresh"; result = persister.Load(id, obj, @event.LockMode, source); source.FetchProfile = previousFetchProfile; // NH Different behavior : we are ignoring transient entities without throw any kind of exception // because a transient entity is "self refreshed" if (!ForeignKeys.IsTransient(persister.EntityName, obj, result == null, @event.Session)) { UnresolvableObjectException.ThrowIfNull(result, id, persister.EntityName); } }