public virtual void OnMerge(MergeEvent @event) { EventCache copyCache = new EventCache(); OnMerge(@event, copyCache); // transientCopyCache may contain parent and child entities in random order. // Child entities occurring ahead of their respective transient parents may fail // to get merged in one iteration. // Retries are necessary as more and more children may be able to merge on subsequent iterations. // Iteratively get transient entities and retry merge until one of the following conditions is true: // 1) transientCopyCache.size() == 0 // 2) transientCopyCache.size() is not decreasing // TODO: find out if retrying can add entities to copyCache (don't think it can...) // For now, just retry once; throw TransientObjectException if there are still any transient entities IDictionary transientCopyCache = this.GetTransientCopyCache(@event, copyCache); while (transientCopyCache.Count > 0) { var initialTransientCount = transientCopyCache.Count; RetryMergeTransientEntities(@event, transientCopyCache, copyCache); // find any entities that are still transient after retry transientCopyCache = this.GetTransientCopyCache(@event, copyCache); // if a retry did nothing, the remaining transient entities // cannot be merged due to references to other transient entities // that are not part of the merge if (transientCopyCache.Count == initialTransientCount) { ISet<string> transientEntityNames = new HashSet<string>(); foreach (object transientEntity in transientCopyCache.Keys) { string transientEntityName = @event.Session.GuessEntityName(transientEntity); transientEntityNames.Add(transientEntityName); log.InfoFormat( "transient instance could not be processed by merge: {0} [{1}]", transientEntityName, transientEntity.ToString()); } throw new TransientObjectException("one or more objects is an unsaved transient instance - save transient instance(s) before merging: " + String.Join(",", transientEntityNames.ToArray())); } } copyCache.Clear(); }
public virtual void OnMerge(MergeEvent @event) { EventCache copyCache = new EventCache(); OnMerge(@event, copyCache); // TODO: iteratively get transient entities and retry merge until one of the following conditions: // 1) transientCopyCache.size() == 0 // 2) transientCopyCache.size() is not decreasing and copyCache.size() is not increasing // TODO: find out if retrying can add entities to copyCache (don't think it can...) // For now, just retry once; throw TransientObjectException if there are still any transient entities IDictionary transientCopyCache = this.GetTransientCopyCache(@event, copyCache); if (transientCopyCache.Count > 0) { RetryMergeTransientEntities(@event, transientCopyCache, copyCache); // find any entities that are still transient after retry transientCopyCache = this.GetTransientCopyCache(@event, copyCache); if (transientCopyCache.Count > 0) { ISet<string> transientEntityNames = new HashedSet<string>(); foreach (object transientEntity in transientCopyCache.Keys) { string transientEntityName = @event.Session.GuessEntityName(transientEntity); transientEntityNames.Add(transientEntityName); log.InfoFormat( "transient instance could not be processed by merge: {0} [{1}]", transientEntityName, transientEntity.ToString()); } throw new TransientObjectException("one or more objects is an unsaved transient instance - save transient instance(s) before merging: " + transientEntityNames); } } copyCache.Clear(); copyCache = null; }
/// <summary> /// Retry merging transient entities /// </summary> /// <param name="event"></param> /// <param name="transientCopyCache"></param> /// <param name="copyCache"></param> protected void RetryMergeTransientEntities(MergeEvent @event, IDictionary transientCopyCache, EventCache copyCache) { // TODO: The order in which entities are saved may matter (e.g., a particular // transient entity may need to be saved before other transient entities can // be saved). // Keep retrying the batch of transient entities until either: // 1) there are no transient entities left in transientCopyCache // or 2) no transient entities were saved in the last batch. // For now, just run through the transient entities and retry the merge foreach(object entity in transientCopyCache.Keys) { object copy = transientCopyCache[entity]; EntityEntry copyEntry = @event.Session.PersistenceContext.GetEntry(copy); if (entity == @event.Entity) MergeTransientEntity(entity, copyEntry.EntityName, @event.RequestedId, @event.Session, copyCache); else MergeTransientEntity(entity, copyEntry.EntityName, copyEntry.Id, @event.Session, copyCache); } }
/// <summary> /// Determine which merged entities in the copyCache are transient. /// </summary> /// <param name="event"></param> /// <param name="copyCache"></param> /// <returns></returns> /// <remarks>Should this method be on the EventCache class?</remarks> protected EventCache GetTransientCopyCache(MergeEvent @event, EventCache copyCache) { EventCache transientCopyCache = new EventCache(); foreach(object entity in copyCache.Keys) { object entityCopy = copyCache[entity]; if (entityCopy.IsProxy()) entityCopy = ((INHibernateProxy)entityCopy).HibernateLazyInitializer.GetImplementation(); // NH-specific: Disregard entities that implement ILifecycle and manage their own state - they // don't have an EntityEntry, and we can't determine if they are transient or not if (entityCopy is ILifecycle) continue; EntityEntry copyEntry = @event.Session.PersistenceContext.GetEntry(entityCopy); if (copyEntry == null) { // entity name will not be available for non-POJO entities // TODO: cache the entity name somewhere so that it is available to this exception log.InfoFormat( "transient instance could not be processed by merge: {0} [{1}]", @event.Session.GuessEntityName(entityCopy), entity); // merge did not cascade to this entity; it's in copyCache because a // different entity has a non-nullable reference to it; // this entity should not be put in transientCopyCache, because it was // not included in the merge; throw new TransientObjectException( "object is an unsaved transient instance - save the transient instance before merging: " + @event.Session.GuessEntityName(entityCopy)); } else if (copyEntry.Status == Status.Saving) { transientCopyCache.Add(entity, entityCopy, copyCache.IsOperatedOn(entity)); } else if (copyEntry.Status != Status.Loaded && copyEntry.Status != Status.ReadOnly) { throw new AssertionFailure( String.Format( "Merged entity does not have status set to MANAGED or READ_ONLY; {0} status = {1}", entityCopy, copyEntry.Status)); } } return transientCopyCache; }
public virtual void OnMerge(MergeEvent @event, IDictionary copiedAlready) { 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 = source.Load(li.EntityName, li.Identifier); return; //EARLY EXIT! } else { entity = li.GetImplementation(); } } 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 = 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> /// Retry merging transient entities /// </summary> /// <param name="event"></param> /// <param name="transientCopyCache"></param> /// <param name="copyCache"></param> protected void RetryMergeTransientEntities(MergeEvent @event, IDictionary transientCopyCache, EventCache copyCache) { // TODO: The order in which entities are saved may matter (e.g., a particular // transient entity may need to be saved before other transient entities can // be saved). // Keep retrying the batch of transient entities until either: // 1) there are no transient entities left in transientCopyCache // or 2) no transient entities were saved in the last batch. // For now, just run through the transient entities and retry the merge foreach (object entity in transientCopyCache.Keys) { object copy = transientCopyCache[entity]; EntityEntry copyEntry = @event.Session.PersistenceContext.GetEntry(copy); if (entity == @event.Entity) { MergeTransientEntity(entity, copyEntry.EntityName, @event.RequestedId, @event.Session, copyCache); } else { MergeTransientEntity(entity, copyEntry.EntityName, copyEntry.Id, @event.Session, copyCache); } } }