/// <summary> Delete any entities that were removed from the collection</summary> private async Task DeleteOrphansAsync(string entityName, IPersistentCollection pc, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); //TODO: suck this logic into the collection! ICollection orphans; if (pc.WasInitialized) { CollectionEntry ce = eventSource.PersistenceContext.GetCollectionEntry(pc); orphans = ce == null ? CollectionHelper.EmptyCollection : await(ce.GetOrphansAsync(entityName, pc, cancellationToken)).ConfigureAwait(false); } else { orphans = await(pc.GetQueuedOrphansAsync(entityName, cancellationToken)).ConfigureAwait(false); } foreach (object orphan in orphans) { if (orphan != null) { log.Info("deleting orphaned entity instance: " + entityName); await(eventSource.DeleteAsync(entityName, orphan, false, null, cancellationToken)).ConfigureAwait(false); } } }
/// <summary> Delete any entities that were removed from the collection</summary> private void DeleteOrphans(string entityName, IPersistentCollection pc) { //TODO: suck this logic into the collection! ICollection orphans; if (pc.WasInitialized) { CollectionEntry ce = eventSource.PersistenceContext.GetCollectionEntry(pc); orphans = ce == null ? CollectionHelper.EmptyCollection : ce.GetOrphans(entityName, pc); } else { orphans = pc.GetQueuedOrphans(entityName); } foreach (object orphan in orphans) { if (orphan != null) { log.Info("deleting orphaned entity instance: {0}", entityName); eventSource.Delete(entityName, orphan, false, null); } } }
/// <summary> /// After a collection was initialized or evicted, we don't /// need to batch fetch it anymore, remove it from the queue /// if necessary /// </summary> /// <param name="ce"></param> public void RemoveBatchLoadableCollection(CollectionEntry ce) { if (batchLoadableCollections.TryGetValue(ce.LoadedPersister.Role, out var map)) { map.Remove(ce); } }
private static void ProcessDereferencedCollection(IPersistentCollection coll, ISessionImplementor session) { IPersistenceContext persistenceContext = session.PersistenceContext; CollectionEntry entry = persistenceContext.GetCollectionEntry(coll); ICollectionPersister loadedPersister = entry.LoadedPersister; if (log.IsDebugEnabled() && loadedPersister != null) { log.Debug("Collection dereferenced: {0}", MessageHelper.CollectionInfoString(loadedPersister, coll, entry.LoadedKey, session)); } // do a check bool hasOrphanDelete = loadedPersister != null && loadedPersister.HasOrphanDelete; if (hasOrphanDelete) { object ownerId = loadedPersister.OwnerEntityPersister.GetIdentifier(coll.Owner); // TODO NH Different behavior //if (ownerId == null) //{ // // the owning entity may have been deleted and its identifier unset due to // // identifier-rollback; in which case, try to look up its identifier from // // the persistence context // if (session.Factory.Settings.IsIdentifierRollbackEnabled) // { // EntityEntry ownerEntry = persistenceContext.GetEntry(coll.Owner); // if (ownerEntry != null) // { // ownerId = ownerEntry.Id; // } // } // if (ownerId == null) // { // throw new AssertionFailure("Unable to determine collection owner identifier for orphan-delete processing"); // } //} EntityKey key = session.GenerateEntityKey(ownerId, loadedPersister.OwnerEntityPersister); object owner = persistenceContext.GetEntity(key); if (owner == null) { throw new AssertionFailure("collection owner not associated with session: " + loadedPersister.Role); } EntityEntry e = persistenceContext.GetEntry(owner); //only collections belonging to deleted entities are allowed to be dereferenced in the case of orphan delete if (e != null && e.Status != Status.Deleted && e.Status != Status.Gone) { throw new HibernateException("A collection with cascade=\"all-delete-orphan\" was no longer referenced by the owning entity instance: " + loadedPersister.Role); } } // do the work entry.CurrentPersister = null; entry.CurrentKey = null; PrepareCollectionForUpdate(coll, entry, session.Factory); }
/// <summary> /// If a CollectionEntry represents a batch loadable collection, add /// it to the queue. /// </summary> /// <param name="collection"></param> /// <param name="ce"></param> public void AddBatchLoadableCollection(IPersistentCollection collection, CollectionEntry ce) { var persister = ce.LoadedPersister; if (!batchLoadableCollections.TryGetValue(persister.Role, out var map)) { map = new LinkedHashMap <CollectionEntry, IPersistentCollection>(); batchLoadableCollections.Add(persister.Role, map); } map[ce] = collection; }
private static void ProcessNeverReferencedCollection(IPersistentCollection coll, ISessionImplementor session) { CollectionEntry entry = session.PersistenceContext.GetCollectionEntry(coll); log.Debug("Found collection with unloaded owner: {0}", MessageHelper.CollectionInfoString(entry.LoadedPersister, coll, entry.LoadedKey, session)); entry.CurrentPersister = entry.LoadedPersister; entry.CurrentKey = entry.LoadedKey; PrepareCollectionForUpdate(coll, entry, session.Factory); }
public PersistentCollectionChangeWorkUnit(ISessionImplementor sessionImplementor, String entityName, AuditConfiguration auditCfg, IPersistentCollection collection, CollectionEntry collectionEntry, Object snapshot, Object id, String referencingPropertyName) : base(sessionImplementor, entityName, auditCfg, new PersistentCollectionChangeWorkUnitId(id, collectionEntry.Role)) { this.ReferencingPropertyName = referencingPropertyName; collectionChanges = auditCfg.EntCfg[EntityName].PropertyMapper .MapCollectionChanges(referencingPropertyName, collection, snapshot, id); }
/// <summary> /// Links the created collection entry with the stored collection key. /// </summary> /// <param name="ce">The collection entry.</param> internal void LinkCollectionEntry(CollectionEntry ce) { if (!_queryCollectionKeys.TryGetValue(ce.LoadedPersister.Role, out var keys) || keys.Count <= 0) { return; } var key = new CollectionKey(ce.LoadedPersister, ce.LoadedKey); if (keys.ContainsKey(key)) { keys[key] = ce; } }
/// <summary> /// Get a batch of uninitialized collection keys for a given role /// </summary> /// <param name="collectionPersister">The persister for the collection role.</param> /// <param name="id">A key that must be included in the batch fetch</param> /// <param name="batchSize">the maximum number of keys to return</param> /// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param> /// <returns>an array of collection keys, of length batchSize (padded with nulls)</returns> public async Task <object[]> GetCollectionBatchAsync(ICollectionPersister collectionPersister, object id, int batchSize, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); object[] keys = new object[batchSize]; keys[0] = id; int i = 1; int end = -1; bool checkForEnd = false; // this only works because collection entries are kept in a sequenced // map by persistence context (maybe we should do like entities and // keep a separate sequences set...) foreach (DictionaryEntry me in context.CollectionEntries) { CollectionEntry ce = (CollectionEntry)me.Value; IPersistentCollection collection = (IPersistentCollection)me.Key; if (!collection.WasInitialized && ce.LoadedPersister == collectionPersister) { if (checkForEnd && i == end) { return(keys); //the first key found after the given key } //if ( end == -1 && count > batchSize*10 ) return keys; //try out ten batches, max bool isEqual = collectionPersister.KeyType.IsEqual(id, ce.LoadedKey, collectionPersister.Factory); if (isEqual) { end = i; //checkForEnd = false; } else if (!await(IsCachedAsync(ce.LoadedKey, collectionPersister, cancellationToken)).ConfigureAwait(false)) { keys[i++] = ce.LoadedKey; //count++; } if (i == batchSize) { i = 1; //end of array, start filling again from start if (end != -1) { checkForEnd = true; } } } } return(keys); //we ran out of keys to try }
/// <summary> /// Initialize the role of the collection. /// </summary> /// <param name="collection">The collection to be updated by reachability. </param> /// <param name="type">The type of the collection. </param> /// <param name="entity">The owner of the collection. </param> /// <param name="session">The session.</param> /// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param> public static Task ProcessReachableCollectionAsync(IPersistentCollection collection, CollectionType type, object entity, ISessionImplementor session, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(Task.FromCanceled <object>(cancellationToken)); } try { collection.Owner = entity; CollectionEntry ce = session.PersistenceContext.GetCollectionEntry(collection); if (ce == null) { // refer to comment in StatefulPersistenceContext.addCollection() return(Task.FromException <object>(new HibernateException(string.Format("Found two representations of same collection: {0}", type.Role)))); } // The CollectionEntry.isReached() stuff is just to detect any silly users // who set up circular or shared references between/to collections. if (ce.IsReached) { // We've been here before return(Task.FromException <object>(new HibernateException(string.Format("Found shared references to a collection: {0}", type.Role)))); } ce.IsReached = true; ISessionFactoryImplementor factory = session.Factory; ICollectionPersister persister = factory.GetCollectionPersister(type.Role); ce.CurrentPersister = persister; ce.CurrentKey = type.GetKeyOfOwner(entity, session); //TODO: better to pass the id in as an argument? if (log.IsDebugEnabled) { log.Debug("Collection found: " + MessageHelper.CollectionInfoString(persister, collection, ce.CurrentKey, session) + ", was: " + MessageHelper.CollectionInfoString(ce.LoadedPersister, collection, ce.LoadedKey, session) + (collection.WasInitialized ? " (initialized)" : " (uninitialized)")); } return(PrepareCollectionForUpdateAsync(collection, ce, factory, cancellationToken)); } catch (System.Exception ex) { return(Task.FromException <object>(ex)); } }
private static Task ProcessNeverReferencedCollectionAsync(IPersistentCollection coll, ISessionImplementor session, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return(Task.FromCanceled <object>(cancellationToken)); } try { CollectionEntry entry = session.PersistenceContext.GetCollectionEntry(coll); log.Debug("Found collection with unloaded owner: " + MessageHelper.CollectionInfoString(entry.LoadedPersister, coll, entry.LoadedKey, session)); entry.CurrentPersister = entry.LoadedPersister; entry.CurrentKey = entry.LoadedKey; return(PrepareCollectionForUpdateAsync(coll, entry, session.Factory, cancellationToken)); } catch (System.Exception ex) { return(Task.FromException <object>(ex)); } }
/// <summary> /// Initialize the role of the collection. /// </summary> /// <param name="collection">The collection to be updated by reachability. </param> /// <param name="type">The type of the collection. </param> /// <param name="entity">The owner of the collection. </param> /// <param name="session">The session.</param> public static void ProcessReachableCollection(IPersistentCollection collection, CollectionType type, object entity, ISessionImplementor session) { collection.Owner = entity; CollectionEntry ce = session.PersistenceContext.GetCollectionEntry(collection); if (ce == null) { // refer to comment in StatefulPersistenceContext.addCollection() throw new HibernateException(string.Format("Found two representations of same collection: {0}", type.Role)); } // The CollectionEntry.isReached() stuff is just to detect any silly users // who set up circular or shared references between/to collections. if (ce.IsReached) { // We've been here before throw new HibernateException(string.Format("Found shared references to a collection: {0}", type.Role)); } ce.IsReached = true; ISessionFactoryImplementor factory = session.Factory; ICollectionPersister persister = factory.GetCollectionPersister(type.Role); ce.CurrentPersister = persister; ce.CurrentKey = type.GetKeyOfOwner(entity, session); //TODO: better to pass the id in as an argument? if (log.IsDebugEnabled()) { log.Debug("Collection found: {0}, was: {1}{2}", MessageHelper.CollectionInfoString(persister, collection, ce.CurrentKey, session), MessageHelper.CollectionInfoString(ce.LoadedPersister, collection, ce.LoadedKey, session), (collection.WasInitialized ? " (initialized)" : " (uninitialized)")); } PrepareCollectionForUpdate(collection, ce, factory); }
/// <summary> /// Checks whether the collection entry was already checked in the cache. /// </summary> /// <param name="persister">The collection persister.</param> /// <param name="entry">The collection entry.</param> /// <returns><see langword="true"/> whether the collection entry was checked, <see langword="false"/> otherwise.</returns> internal bool WasCollectionEntryChecked(ICollectionPersister persister, CollectionEntry entry) { return(_queryCheckedCollectionEntries.TryGetValue(persister.Role, out var checkedEntries) && checkedEntries.Contains(entry)); }
/// <summary> /// Retrives the uninitialized persistent collection from the queue. /// </summary> /// <param name="persister">The collection persister.</param> /// <param name="ce">The collection entry.</param> /// <returns>A persistent collection if found, <see langword="null"/> otherwise.</returns> internal IPersistentCollection GetBatchLoadableCollection(ICollectionPersister persister, CollectionEntry ce) { if (!batchLoadableCollections.TryGetValue(persister.Role, out var map)) { return(null); } if (!map.TryGetValue(ce, out var collection)) { return(null); } return(collection); }
private static void PrepareCollectionForUpdate(IPersistentCollection collection, CollectionEntry entry, ISessionFactoryImplementor factory) { //1. record the collection role that this collection is referenced by //2. decide if the collection needs deleting/creating/updating (but don't actually schedule the action yet) if (entry.IsProcessed) { throw new AssertionFailure("collection was processed twice by flush()"); } entry.IsProcessed = true; ICollectionPersister loadedPersister = entry.LoadedPersister; ICollectionPersister currentPersister = entry.CurrentPersister; if (loadedPersister != null || currentPersister != null) { // it is or was referenced _somewhere_ bool ownerChanged = loadedPersister != currentPersister || !currentPersister.KeyType.IsEqual(entry.LoadedKey, entry.CurrentKey, factory); if (ownerChanged) { // do a check bool orphanDeleteAndRoleChanged = loadedPersister != null && currentPersister != null && loadedPersister.HasOrphanDelete; if (orphanDeleteAndRoleChanged) { throw new HibernateException("Don't change the reference to a collection with cascade=\"all-delete-orphan\": " + loadedPersister.Role); } // do the work if (currentPersister != null) { entry.IsDorecreate = true; // we will need to create new entries } if (loadedPersister != null) { entry.IsDoremove = true; // we will need to remove ye olde entries if (entry.IsDorecreate) { log.Debug("Forcing collection initialization"); collection.ForceInitialization(); // force initialize! } } } else if (collection.IsDirty) { // else if it's elements changed entry.IsDoupdate = true; } } }
/// <summary> /// Get a batch of all uninitialized collection keys for a given role that are present in the cached query. /// Once this method is called the uninitialized collection keys for a given role will be cleared in order to prevent /// double checking the same keys. /// </summary> /// <param name="collectionPersister">The persister for the collection role.</param> /// <param name="key">A key that must be included in the batch fetch.</param> /// <param name="collectionEntries">An array that will be filled with collection entries if set.</param> /// <returns> /// An array of collection keys that can be empty if the key was already checked or <see langword="null" /> /// if the key is not present in the cached query. /// </returns> internal object[] GetCollectionBatch(ICollectionPersister collectionPersister, object key, out CollectionEntry[] collectionEntries) { if (!_queryCollectionKeys.TryGetValue(collectionPersister.Role, out var keys)) { collectionEntries = null; return(null); // The collection was not present in the cached query } var collectionKey = new CollectionKey(collectionPersister, key); if (_queryCheckedCollectionKeys.TryGetValue(collectionPersister.Role, out var checkedKeys) && checkedKeys.Contains(collectionKey)) { collectionEntries = null; return(Array.Empty <object>()); } if (!keys.TryGetValue(collectionKey, out var collectionEntry) || collectionEntry == null) { collectionEntries = null; return(null); // The collection was not present in the cached query } if (checkedKeys == null) { checkedKeys = new HashSet <CollectionKey>(); _queryCheckedCollectionKeys.Add(collectionPersister.Role, checkedKeys); } if (!_queryCheckedCollectionEntries.TryGetValue(collectionPersister.Role, out var checkedEntries)) { checkedEntries = new HashSet <CollectionEntry>(); _queryCheckedCollectionEntries.Add(collectionPersister.Role, checkedEntries); } var result = new object[keys.Count]; collectionEntries = new CollectionEntry[result.Length]; var i = 0; result[i++] = key; foreach (var pair in keys) { if (pair.Value == null || _persistenceContext.GetCollection(pair.Key)?.WasInitialized != false) { continue; // The collection was not registered or is already initialized } if (collectionPersister.KeyType.IsEqual(key, pair.Value.LoadedKey, collectionPersister.Factory)) { collectionEntries[0] = pair.Value; checkedKeys.Add(pair.Key); checkedEntries.Add(pair.Value); continue; } collectionEntries[i] = pair.Value; result[i++] = pair.Value.LoadedKey; checkedKeys.Add(pair.Key); checkedEntries.Add(pair.Value); } keys.Clear(); return(result); }
private void OnCollectionAction(AbstractCollectionEvent evt, IPersistentCollection newColl, object oldColl, CollectionEntry collectionEntry) { if (!VerCfg.GlobalCfg.GenerateRevisionsForCollections) return; var entityName = evt.GetAffectedOwnerEntityName(); if (!VerCfg.EntCfg.IsVersioned(entityName)) return; var verSync = VerCfg.AuditProcessManager.Get(evt.Session); var ownerEntityName = ((AbstractCollectionPersister)collectionEntry.LoadedPersister).OwnerEntityName; var referencingPropertyName = collectionEntry.Role.Substring(ownerEntityName.Length + 1); // Checking if this is not a "fake" many-to-one bidirectional relation. The relation description may be // null in case of collections of non-entities. var rd = VerCfg.EntCfg[entityName].GetRelationDescription(referencingPropertyName); if (rd != null && rd.MappedByPropertyName != null) { GenerateFakeBidirecationalRelationWorkUnits(verSync, newColl, oldColl, entityName, referencingPropertyName, evt, rd); } else { var workUnit = new PersistentCollectionChangeWorkUnit(evt.Session, entityName, VerCfg, newColl, collectionEntry, oldColl, evt.AffectedOwnerIdOrNull, referencingPropertyName); verSync.AddWorkUnit(workUnit); if (workUnit.ContainsWork()) { // There are some changes: a revision needs also be generated for the collection owner verSync.AddWorkUnit(new CollectionChangeWorkUnit(evt.Session, evt.GetAffectedOwnerEntityName(), VerCfg, evt.AffectedOwnerIdOrNull, evt.AffectedOwnerOrNull)); GenerateBidirectionalCollectionChangeWorkUnits(verSync, evt, workUnit, rd); } } }