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;
		}
Пример #5
0
        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));
                    }
                }
            }
        }
Пример #6
0
        /// <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);
                }
            }
        }