From: <jul...@us...> - 2011-02-11 16:07:48
|
Revision: 5380 http://nhibernate.svn.sourceforge.net/nhibernate/?rev=5380&view=rev Author: julian-maughan Date: 2011-02-11 16:07:38 +0000 (Fri, 11 Feb 2011) Log Message: ----------- Port of Hibernate read-only entities feature, including tests (NH-908) Modified Paths: -------------- branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/EntityEntry.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/IPersistenceContext.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/QueryParameters.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/StatefulPersistenceContext.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/TwoPhaseLoad.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Event/Default/AbstractFlushingEventListener.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Event/Default/AbstractReassociateEventListener.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Event/Default/AbstractSaveEventListener.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Event/Default/DefaultAutoFlushEventListener.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Event/Default/DefaultDeleteEventListener.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Event/Default/DefaultEvictEventListener.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Event/Default/DefaultFlushEventListener.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Event/Default/DefaultLoadEventListener.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Event/Default/DefaultRefreshEventListener.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Event/Default/DefaultReplicateEventListener.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Event/Default/DefaultSaveOrUpdateEventListener.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Hql/Ast/ANTLR/Loader/QueryLoader.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Hql/Ast/ANTLR/QueryTranslatorImpl.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Hql/Classic/QueryTranslator.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Hql/IQueryTranslator.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/ICriteria.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/IQuery.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/ISession.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Impl/AbstractQueryImpl.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Impl/CriteriaImpl.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Impl/EnumerableImpl.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Impl/SessionImpl.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Impl/StatelessSessionImpl.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Loader/Criteria/CriteriaQueryTranslator.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Loader/Loader.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Persister/Entity/AbstractEntityPersister.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Proxy/AbstractLazyInitializer.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate/Proxy/ILazyInitializer.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Legacy/FooBarTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/NHibernate.Test.csproj Added Paths: ----------- branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/ branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/Contract.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/ContractVariation.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/ContractVariation.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/ branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/AbstractEntityWithManyToManyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/AbstractEntityWithOneToManyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Contract.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/ContractVariation.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Info.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/ branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/ContractVariation.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/ContractVariationOneToManyJoin.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/ContractVariationVersioned.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/ContractVariationVersionedOneToManyJoin.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/EntityWithInverseManyToManyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/EntityWithInverseOneToManyJoinTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/EntityWithInverseOneToManyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/VersionedEntityWithInverseManyToManyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/VersionedEntityWithInverseOneToManyFailureExpectedTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/VersionedEntityWithInverseOneToManyJoinFailureExpectedTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/VersionedEntityWithInverseOneToManyJoinTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Inverse/VersionedEntityWithInverseOneToManyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/ branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/ContractVariation.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/ContractVariationOneToManyJoin.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/ContractVariationUnidir.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/ContractVariationVersioned.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/ContractVariationVersionedOneToManyJoin.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/EntityWithNonInverseManyToManyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/EntityWithNonInverseManyToManyUnidirTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/EntityWithNonInverseOneToManyJoinTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/EntityWithNonInverseOneToManyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/EntityWithNonInverseOneToManyUnidirTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/VersionedEntityWithNonInverseManyToManyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/VersionedEntityWithNonInverseOneToManyJoinTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/NonInverse/VersionedEntityWithNonInverseOneToManyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Owner.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Party.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/EntityWithMutableCollection/Plan.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/ImmutableTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/Info.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/Party.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/Immutable/Plan.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/ branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/AbstractReadOnlyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/Container.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/Course.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/DataPoint.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/DataPoint.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/Enrolment.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/Enrolment.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/Info.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/Owner.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/ReadOnlyCriteriaQueryTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/ReadOnlyProxyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/ReadOnlySessionLazyNonLazyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/ReadOnlySessionTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/ReadOnlyTest.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/ReadOnlyVersionedNodes.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/Student.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/StudentDto.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/TextHolder.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/TextHolder.hbm.xml branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/VersionedNode.cs branches/ReadOnlyEntities/nhibernate/src/NHibernate.Test/ReadOnly/VersionedNode.hbm.xml Modified: branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/EntityEntry.cs =================================================================== --- branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/EntityEntry.cs 2011-02-11 14:44:33 UTC (rev 5379) +++ branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/EntityEntry.cs 2011-02-11 16:07:38 UTC (rev 5380) @@ -14,6 +14,7 @@ { private LockMode lockMode; private Status status; + private Status? previousStatus; private readonly object id; private object[] loadedState; private object[] deletedState; @@ -21,10 +22,11 @@ private object version; [NonSerialized] - private IEntityPersister persister;// for convenience to save some lookups + private IEntityPersister persister; // for convenience to save some lookups private readonly EntityMode entityMode; private readonly string entityName; + private EntityKey cachedEntityKey; private readonly bool isBeingReplicated; private readonly bool loadedWithLazyPropertiesUnfetched; @@ -50,9 +52,11 @@ bool disableVersionIncrement, bool lazyPropertiesAreUnfetched) { this.status = status; - this.loadedState = loadedState; + this.previousStatus = null; + // only retain loaded state if the status is not Status.ReadOnly + if (status != Status.ReadOnly) { this.loadedState = loadedState; } + this.id = id; this.rowId = rowId; - this.id = id; this.existsInDatabase = existsInDatabase; this.version = version; this.lockMode = lockMode; @@ -74,7 +78,7 @@ } /// <summary> - /// Gets or sets the <see cref="Status"/> of this Entity with respect to its + /// Gets or sets the <see cref="Status"/> of this Entity with respect to its /// persistence in the database. /// </summary> /// <value>The <see cref="Status"/> of this Entity.</value> @@ -86,7 +90,11 @@ if (value == Status.ReadOnly) loadedState = null; //memory optimization - status = value; + if (this.status != value) + { + previousStatus = this.status; + this.status = value; + } } } @@ -94,7 +102,7 @@ /// Gets or sets the identifier of the Entity in the database. /// </summary> /// <value>The identifier of the Entity in the database if one has been assigned.</value> - /// <remarks>This might be <see langword="null" /> when the <see cref="EntityEntry.Status"/> is + /// <remarks>This might be <see langword="null" /> when the <see cref="EntityEntry.Status"/> is /// <see cref="Engine.Status.Saving"/> and the database generates the id.</remarks> public object Id { @@ -129,7 +137,7 @@ /// </summary> /// <value><see langword="true" /> if it is already in the database.</value> /// <remarks> - /// It can also be <see langword="true" /> if it does not exists in the database yet and the + /// It can also be <see langword="true" /> if it does not exists in the database yet and the /// <see cref="IEntityPersister.IsIdentifierAssignedByInsert"/> is <see langword="true" />. /// </remarks> public bool ExistsInDatabase @@ -179,15 +187,33 @@ { get { return loadedWithLazyPropertiesUnfetched; } } + + /// <summary> + /// Get the EntityKey based on this EntityEntry. + /// </summary> + public EntityKey EntityKey + { + get + { + if (cachedEntityKey == null) + { + if (id == null) + throw new InvalidOperationException("cannot generate an EntityKey when id is null."); + cachedEntityKey = new EntityKey(id, persister, entityMode); + } + return cachedEntityKey; + } + } + public object GetLoadedValue(string propertyName) { int propertyIndex = ((IUniqueKeyLoadable) persister).GetPropertyIndex(propertyName); return loadedState[propertyIndex]; } - /// <summary> - /// After actually inserting a row, record the fact that the instance exists on the + /// <summary> + /// After actually inserting a row, record the fact that the instance exists on the /// database (needed for identity-column key generation) /// </summary> public void PostInsert() @@ -213,12 +239,13 @@ FieldInterceptionHelper.ClearDirty(entity); } - /// <summary> + /// <summary> /// After actually deleting a row, record the fact that the instance no longer /// exists in the database /// </summary> public void PostDelete() { + previousStatus = status; status = Status.Gone; existsInDatabase = false; } @@ -230,7 +257,7 @@ LockMode = LockMode.Force; persister.SetPropertyValue(entity, Persister.VersionProperty, nextVersion, entityMode); } - + public bool IsNullifiable(bool earlyInsert, ISessionImplementor session) { return Status == Status.Saving || (earlyInsert ? !ExistsInDatabase : session.PersistenceContext.NullifiableEntityKeys.Contains(new EntityKey(Id, Persister, entityMode))); @@ -238,21 +265,42 @@ public bool RequiresDirtyCheck(object entity) { - bool isMutableInstance = status != Status.ReadOnly && persister.IsMutable; - return - isMutableInstance - && - (Persister.HasMutableProperties || !FieldInterceptionHelper.IsInstrumented(entity) - || FieldInterceptionHelper.ExtractFieldInterceptor(entity).IsDirty); + IsModifiableEntity() + && (Persister.HasMutableProperties || !FieldInterceptionHelper.IsInstrumented(entity) + || FieldInterceptionHelper.ExtractFieldInterceptor(entity).IsDirty); } + + /// <summary> + /// Can the entity be modified? + /// The entity is modifiable if all of the following are true: + /// - the entity class is mutable + /// - the entity is not read-only + /// - if the current status is Status.Deleted, then the entity was not read-only when it was deleted + /// </summary> + /// <returns>true, if the entity is modifiable; false, otherwise</returns> + public bool IsModifiableEntity() + { + return (status != Status.ReadOnly) && !(status == Status.Deleted && previousStatus == Status.ReadOnly) && Persister.IsMutable; + } + + public bool IsReadOnly + { + get + { + if (status != Status.Loaded && status != Status.ReadOnly) + { + throw new HibernateException("instance was not in a valid state"); + } + return status == Status.ReadOnly; + } + } public void SetReadOnly(bool readOnly, object entity) { - if (status != Status.Loaded && status != Status.ReadOnly) - { - throw new HibernateException("instance was not in a valid state"); - } + if (readOnly == IsReadOnly) + return; // simply return since the status is not being changed + if (readOnly) { Status = Status.ReadOnly; @@ -260,6 +308,9 @@ } else { + if (!persister.IsMutable) + throw new InvalidOperationException("Cannot make an immutable entity modifiable."); + Status = Status.Loaded; loadedState = Persister.GetPropertyValues(entity, entityMode); } Modified: branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/IPersistenceContext.cs =================================================================== --- branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/IPersistenceContext.cs 2011-02-11 14:44:33 UTC (rev 5379) +++ branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/IPersistenceContext.cs 2011-02-11 16:07:38 UTC (rev 5380) @@ -9,25 +9,25 @@ namespace NHibernate.Engine { - /// <summary> - /// Holds the state of the persistence context, including the - /// first-level cache, entries, snapshots, proxies, etc. + /// <summary> + /// Holds the state of the persistence context, including the + /// first-level cache, entries, snapshots, proxies, etc. /// </summary> public interface IPersistenceContext { bool IsStateless { get;} - /// <summary> - /// Get the session to which this persistence context is bound. + /// <summary> + /// Get the session to which this persistence context is bound. /// </summary> ISessionImplementor Session { get;} - /// <summary> + /// <summary> /// Retrieve this persistence context's managed load context. /// </summary> LoadContexts LoadContexts { get;} - /// <summary> + /// <summary> /// Get the <tt>BatchFetchQueue</tt>, instantiating one if necessary. /// </summary> BatchFetchQueue BatchFetchQueue { get;} @@ -52,12 +52,47 @@ /// <summary>Is a flush cycle currently in process?</summary> /// <remarks>Called before and after the flushcycle</remarks> - bool Flushing { get;set;} + bool Flushing { get; set;} + + /// <summary> + /// Change the default for entities and proxies loaded into this persistence + /// context from modifiable to read-only mode, or from modifiable to read-only + /// mode. + /// </summary> + /// <remarks> + /// <para> + /// Read-only entities are not dirty-checked and snapshots of persistent + /// state are not maintained. Read-only entities can be modified, but + /// changes are not persisted. + /// </para> + /// <para> + /// When a proxy is initialized, the loaded entity will have the same + /// read-only/modifiable setting as the uninitialized + /// proxy has, regardless of the persistence context's current setting. + /// </para> + /// <para> + /// To change the read-only/modifiable setting for a particular entity + /// or proxy that is already in this session: + /// <see cref="SetReadOnly(object, bool)" /> + /// <see cref="ISession.SetReadOnly(object, bool)" /> + /// </para> + /// <para> + /// To determine the read-only/modifiable setting for a particular entity or proxy: + /// <see cref="IsReadOnly(object)" /> + /// <see cref="ISession.IsReadOnly(object)" /> + /// </para> + /// <para> + /// To override this session's read-only/modifiable setting for entities + /// and proxies loaded by an IQuery: + /// <see cref="IQuery.SetReadOnly(bool)" /> + /// </para> + /// </remarks> + bool DefaultReadOnly { get; set; } /// <summary> Add a collection which has no owner loaded</summary> void AddUnownedCollection(CollectionKey key, IPersistentCollection collection); - /// <summary> + /// <summary> /// Get and remove a collection whose owner is not yet loaded, /// when its owner is being loaded /// </summary> @@ -75,13 +110,13 @@ /// <summary> Called after transactions end</summary> void AfterTransactionCompletion(); - /// <summary> + /// <summary> /// Get the current state of the entity as known to the underlying - /// database, or null if there is no corresponding row + /// database, or null if there is no corresponding row /// </summary> object[] GetDatabaseSnapshot(object id, IEntityPersister persister); - /// <summary> + /// <summary> /// Retrieve the cached database snapshot for the requested entity key. /// </summary> /// <param name="key">The entity key for which to retrieve the cached snapshot </param> @@ -95,9 +130,9 @@ /// </remarks> object[] GetCachedDatabaseSnapshot(EntityKey key); - /// <summary> - /// Get the values of the natural id fields as known to the underlying - /// database, or null if the entity has no natural id or there is no + /// <summary> + /// Get the values of the natural id fields as known to the underlying + /// database, or null if the entity has no natural id or there is no /// corresponding row. /// </summary> object[] GetNaturalIdSnapshot(object id, IEntityPersister persister); @@ -105,7 +140,7 @@ /// <summary> Add a canonical mapping from entity key to entity instance</summary> void AddEntity(EntityKey key, object entity); - /// <summary> + /// <summary> /// Get the entity instance associated with the given <tt>EntityKey</tt> /// </summary> object GetEntity(EntityKey key); @@ -113,7 +148,7 @@ /// <summary> Is there an entity with the given key in the persistence context</summary> bool ContainsEntity(EntityKey key); - /// <summary> + /// <summary> /// Remove an entity from the session cache, also clear /// up other state associated with the entity, all except /// for the <tt>EntityEntry</tt> @@ -126,8 +161,8 @@ /// <summary> Add an entity to the cache by unique key</summary> void AddEntity(EntityUniqueKey euk, object entity); - /// <summary> - /// Retrieve the EntityEntry representation of the given entity. + /// <summary> + /// Retrieve the EntityEntry representation of the given entity. /// </summary> /// <param name="entity">The entity for which to locate the EntityEntry. </param> /// <returns> The EntityEntry for the given entity. </returns> @@ -147,8 +182,8 @@ LockMode lockMode, bool existsInDatabase, IEntityPersister persister, bool disableVersionIncrement, bool lazyPropertiesAreUnfetched); - /// <summary> - /// Generates an appropriate EntityEntry instance and adds it + /// <summary> + /// Generates an appropriate EntityEntry instance and adds it /// to the event source's internal caches. /// </summary> EntityEntry AddEntry(object entity, Status status, object[] loadedState, object rowId, object id, object version, @@ -161,34 +196,34 @@ /// <summary> Is the given proxy associated with this persistence context?</summary> bool ContainsProxy(INHibernateProxy proxy); - /// <summary> - /// Takes the given object and, if it represents a proxy, reassociates it with this event source. + /// <summary> + /// Takes the given object and, if it represents a proxy, reassociates it with this event source. /// </summary> /// <param name="value">The possible proxy to be reassociated. </param> /// <returns> Whether the passed value represented an actual proxy which got initialized. </returns> bool ReassociateIfUninitializedProxy(object value); - /// <summary> + /// <summary> /// If a deleted entity instance is re-saved, and it has a proxy, we need to - /// reset the identifier of the proxy + /// reset the identifier of the proxy /// </summary> void ReassociateProxy(object value, object id); - /// <summary> + /// <summary> /// Get the entity instance underlying the given proxy, throwing /// an exception if the proxy is uninitialized. If the given object /// is not a proxy, simply return the argument. /// </summary> object Unproxy(object maybeProxy); - /// <summary> - /// Possibly unproxy the given reference and reassociate it with the current session. + /// <summary> + /// Possibly unproxy the given reference and reassociate it with the current session. /// </summary> /// <param name="maybeProxy">The reference to be unproxied if it currently represents a proxy. </param> /// <returns> The unproxied instance. </returns> object UnproxyAndReassociate(object maybeProxy); - /// <summary> + /// <summary> /// Attempts to check whether the given key represents an entity already loaded within the /// current session. /// </summary> @@ -196,11 +231,11 @@ /// <param name="key">The entity key.</param> void CheckUniqueness(EntityKey key, object obj); - /// <summary> + /// <summary> /// If the existing proxy is insufficiently "narrow" (derived), instantiate a new proxy /// and overwrite the registration of the old one. This breaks == and occurs only for /// "class" proxies rather than "interface" proxies. Also init the proxy to point to - /// the given target implementation if necessary. + /// the given target implementation if necessary. /// </summary> /// <param name="proxy">The proxy instance to be narrowed. </param> /// <param name="persister">The persister for the proxied entity. </param> @@ -209,14 +244,14 @@ /// <returns> An appropriately narrowed instance. </returns> object NarrowProxy(INHibernateProxy proxy, IEntityPersister persister, EntityKey key, object obj); - /// <summary> + /// <summary> /// Return the existing proxy associated with the given <tt>EntityKey</tt>, or the /// third argument (the entity associated with the key) if no proxy exists. Init /// the proxy to the target implementation, if necessary. /// </summary> object ProxyFor(IEntityPersister persister, EntityKey key, object impl); - /// <summary> + /// <summary> /// Return the existing proxy associated with the given <tt>EntityKey</tt>, or the /// argument (the entity associated with the key) if no proxy exists. /// (slower than the form above) @@ -228,7 +263,7 @@ /// <summary> Get the entity that owned this persistent collection when it was loaded </summary> /// <param name="collection">The persistent collection </param> - /// <returns> + /// <returns> /// The owner if its entity ID is available from the collection's loaded key /// and the owner entity is in the persistence context; otherwise, returns null /// </returns> @@ -245,7 +280,7 @@ /// <summary> add a detached uninitialized collection</summary> void AddUninitializedDetachedCollection(ICollectionPersister persister, IPersistentCollection collection); - /// <summary> + /// <summary> /// Add a new collection (ie. a newly created one, just instantiated by the /// application, with no database state or snapshot) /// </summary> @@ -253,7 +288,7 @@ /// <param name="persister"></param> void AddNewCollection(ICollectionPersister persister, IPersistentCollection collection); - /// <summary> + /// <summary> /// add an (initialized) collection that was created by another session and passed /// into update() (ie. one with a snapshot and existing state on the database) /// </summary> @@ -265,12 +300,12 @@ /// <summary> Get the collection instance associated with the <tt>CollectionKey</tt></summary> IPersistentCollection GetCollection(CollectionKey collectionKey); - /// <summary> + /// <summary> /// Register a collection for non-lazy loading at the end of the two-phase load /// </summary> void AddNonLazyCollection(IPersistentCollection collection); - /// <summary> + /// <summary> /// Force initialization of all non-lazy collections encountered during /// the current two-phase load (actually, this is a no-op, unless this /// is the "outermost" load) @@ -281,12 +316,12 @@ IPersistentCollection GetCollectionHolder(object array); /// <summary> Register a <tt>PersistentCollection</tt> object for an array. - /// Associates a holder with an array - MUST be called after loading + /// Associates a holder with an array - MUST be called after loading /// array, since the array instance is not created until endLoad(). /// </summary> void AddCollectionHolder(IPersistentCollection holder); - /// <summary> + /// <summary> /// Remove the mapping of collection to holder during eviction of the owning entity /// </summary> IPersistentCollection RemoveCollectionHolder(object array); @@ -294,7 +329,7 @@ /// <summary> Get the snapshot of the pre-flush collection state</summary> object GetSnapshot(IPersistentCollection coll); - /// <summary> + /// <summary> /// Get the collection entry for a collection passed to filter, /// which might be a collection wrapper, an array, or an unwrapped /// collection. Return null if there is no entry. @@ -322,18 +357,18 @@ /// <summary> Call this after finishing a two-phase load</summary> void AfterLoad(); - /// <summary> + /// <summary> /// Search the persistence context for an owner for the child object, /// given a collection role /// </summary> object GetOwnerId(string entity, string property, object childObject, IDictionary mergeMap); - /// <summary> + /// <summary> /// Search the persistence context for an index of the child object, given a collection role /// </summary> object GetIndexInOwner(string entity, string property, object childObject, IDictionary mergeMap); - /// <summary> + /// <summary> /// Record the fact that the association belonging to the keyed entity is null. /// </summary> void AddNullProperty(EntityKey ownerKey, string propertyName); @@ -341,9 +376,56 @@ /// <summary> Is the association property belonging to the keyed entity null?</summary> bool IsPropertyNull(EntityKey ownerKey, string propertyName); - /// <summary> Set the object to read only and discard it's snapshot</summary> + /// <summary> + /// Set the entity or proxy to read only and discard it's snapshot. + /// Set an unmodified persistent object to read-only mode, or a read-only + /// object to modifiable mode. + /// </summary> + /// <remarks> + /// <para> + /// Read-only entities are not dirty-checked and snapshots of persistent + /// state are not maintained. Read-only entities can be modified, but + /// changes are not persisted. + /// </para> + /// <para> + /// When a proxy is initialized, the loaded entity will have the same + /// read-only/modifiable setting as the uninitialized + /// proxy has, regardless of the session's current setting. + /// </para> + /// <para> + /// If the entity or proxy already has the specified read-only/modifiable + /// setting, then this method does nothing. + /// </para> + /// <para> + /// To set the default read-only/modifiable setting used for + /// entities and proxies that are loaded into this persistence context: + /// <see cref="IPersistenceContext.DefaultReadOnly" /> + /// <see cref="ISession.DefaultReadOnly" /> + /// </para> + /// <para> + /// To override this persistence context's read-only/modifiable setting + /// for entities and proxies loaded by a Query: + /// <see cref="IQuery.SetReadOnly(bool)" /> + /// </para> + /// </remarks> + /// <param name="entity">An entity or INHibernateProxy</param> + /// <param name="readOnly">if <c>true</c>, the entity or proxy is made read-only; if <c>false</c>, the entity or proxy is made modifiable.</param> + /// <seealso cref="ISession.SetReadOnly(object, bool)" /> void SetReadOnly(object entity, bool readOnly); + /// <summary> + /// Is the entity or proxy read-only? + /// </summary> + /// <remarks> + /// To get the default read-only/modifiable setting used for + /// entities and proxies that are loaded into the session: + /// <see cref="ISession.DefaultReadOnly" /> + /// </remarks> + /// <returns> + /// <c>true</c>, the object is read-only; <c>false</c>, the object is modifiable. + /// </returns> + bool IsReadOnly(object entityOrProxy); + void ReplaceDelayedEntityIdentityInsertKeys(EntityKey oldKey, object generatedId); } } Modified: branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/QueryParameters.cs =================================================================== --- branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/QueryParameters.cs 2011-02-11 14:44:33 UTC (rev 5379) +++ branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/QueryParameters.cs 2011-02-11 16:07:38 UTC (rev 5380) @@ -15,7 +15,7 @@ namespace NHibernate.Engine { /// <summary> - /// Container for data that is used during the NHibernate query/load process. + /// Container for data that is used during the NHibernate query/load process. /// </summary> [Serializable] public sealed class QueryParameters @@ -39,6 +39,7 @@ private object _optionalObject; private string _optionalEntityName; private object _optionalId; + private bool _isReadOnlyInitialized; private string _comment; private bool _readOnly; private int? limitParameterIndex = null; @@ -46,18 +47,14 @@ private IDictionary<int, int> _adjustedParameterLocations; private IDictionary<int, int> _tempPagingParameterIndexes; private IDictionary<int, int> _pagingParameterIndexMap; - private SqlString processedSQL; - private readonly IResultTransformer _resultTransformer; - // not implemented: private ScrollMode _scrollMode; - + public QueryParameters() : this(ArrayHelper.EmptyTypeArray, ArrayHelper.EmptyObjectArray) {} public QueryParameters(IType type, object value) : this(new[] {type}, new[] {value}) {} - public QueryParameters(IType[] positionalParameterTypes, object[] postionalParameterValues, object optionalObject, - string optionalEntityName, object optionalObjectId) + public QueryParameters(IType[] positionalParameterTypes, object[] postionalParameterValues, object optionalObject, string optionalEntityName, object optionalObjectId) : this(positionalParameterTypes, postionalParameterValues) { _optionalObject = optionalObject; @@ -66,32 +63,22 @@ } public QueryParameters(IType[] positionalParameterTypes, object[] postionalParameterValues) - : this(positionalParameterTypes, postionalParameterValues, null, null, false, null, null, false, null, null) {} + : this(positionalParameterTypes, postionalParameterValues, null, null, false, false, false, null, null, false, null, null) {} public QueryParameters(IType[] positionalParameterTypes, object[] postionalParameterValues, object[] collectionKeys) : this(positionalParameterTypes, postionalParameterValues, null, collectionKeys) {} - public QueryParameters(IType[] positionalParameterTypes, object[] postionalParameterValues, - IDictionary<string, TypedValue> namedParameters, object[] collectionKeys) - : this( - positionalParameterTypes, postionalParameterValues, namedParameters, null, null, false, false, null, null, - collectionKeys, null) {} + public QueryParameters(IType[] positionalParameterTypes, object[] postionalParameterValues, IDictionary<string, TypedValue> namedParameters, object[] collectionKeys) + : this(positionalParameterTypes, postionalParameterValues, namedParameters, null, null, false, false, false, null, null, collectionKeys, null) {} - public QueryParameters(IType[] positionalParameterTypes, object[] positionalParameterValues, - IDictionary<string, LockMode> lockModes, RowSelection rowSelection, bool cacheable, - string cacheRegion, string comment, bool isLookupByNaturalKey, IResultTransformer transformer, IDictionary<int,int> tempPagingParameterIndexes) - : this( - positionalParameterTypes, positionalParameterValues, null, lockModes, rowSelection, false, cacheable, cacheRegion, - comment, null, transformer) + public QueryParameters(IType[] positionalParameterTypes, object[] positionalParameterValues, IDictionary<string, LockMode> lockModes, RowSelection rowSelection, bool isReadOnlyInitialized, bool readOnly, bool cacheable, string cacheRegion, string comment, bool isLookupByNaturalKey, IResultTransformer transformer, IDictionary<int,int> tempPagingParameterIndexes) + : this(positionalParameterTypes, positionalParameterValues, null, lockModes, rowSelection, isReadOnlyInitialized, readOnly, cacheable, cacheRegion, comment, null, transformer) { NaturalKeyLookup = isLookupByNaturalKey; _tempPagingParameterIndexes = tempPagingParameterIndexes; } - public QueryParameters(IType[] positionalParameterTypes, object[] positionalParameterValues, - IDictionary<string, TypedValue> namedParameters, IDictionary<string, LockMode> lockModes, - RowSelection rowSelection, bool readOnly, bool cacheable, string cacheRegion, string comment, - object[] collectionKeys, IResultTransformer transformer) + public QueryParameters(IType[] positionalParameterTypes, object[] positionalParameterValues, IDictionary<string, TypedValue> namedParameters, IDictionary<string, LockMode> lockModes, RowSelection rowSelection, bool isReadOnlyInitialized, bool readOnly, bool cacheable, string cacheRegion, string comment, object[] collectionKeys, IResultTransformer transformer) { _positionalParameterTypes = positionalParameterTypes; _positionalParameterValues = positionalParameterValues; @@ -102,25 +89,19 @@ _cacheRegion = cacheRegion; _comment = comment; _collectionKeys = collectionKeys; + _isReadOnlyInitialized = isReadOnlyInitialized; _readOnly = readOnly; _resultTransformer = transformer; } - public QueryParameters(IType[] positionalParameterTypes, object[] positionalParameterValues, - IDictionary<string, TypedValue> namedParameters, IDictionary<string, LockMode> lockModes, - RowSelection rowSelection, bool readOnly, bool cacheable, string cacheRegion, string comment, - object[] collectionKeys, object optionalObject, string optionalEntityName, object optionalId, - IResultTransformer transformer) - : this( - positionalParameterTypes, positionalParameterValues, namedParameters, lockModes, rowSelection, readOnly, cacheable, - cacheRegion, comment, collectionKeys, transformer) + public QueryParameters(IType[] positionalParameterTypes, object[] positionalParameterValues, IDictionary<string, TypedValue> namedParameters, IDictionary<string, LockMode> lockModes, RowSelection rowSelection, bool isReadOnlyInitialized, bool readOnly, bool cacheable, string cacheRegion, string comment, object[] collectionKeys, object optionalObject, string optionalEntityName, object optionalId, IResultTransformer transformer) + : this(positionalParameterTypes, positionalParameterValues, namedParameters, lockModes, rowSelection, isReadOnlyInitialized, readOnly, cacheable, cacheRegion, comment, collectionKeys, transformer) { _optionalEntityName = optionalEntityName; _optionalId = optionalId; _optionalObject = optionalObject; } - /// <summary></summary> public bool HasRowSelection { get { return _rowSelection != null; } @@ -136,9 +117,6 @@ get { return offsetParameterIndex; } } - /// <summary> - /// Named parameters. - /// </summary> public IDictionary<string, TypedValue> NamedParameters { get { return _namedParameters; } @@ -146,7 +124,7 @@ } /// <summary> - /// Gets or sets an array of <see cref="IType"/> objects that is stored at the index + /// Gets or sets an array of <see cref="IType"/> objects that is stored at the index /// of the Parameter. /// </summary> public IType[] PositionalParameterTypes @@ -161,7 +139,7 @@ } /// <summary> - /// Gets or sets an array of <see cref="object"/> objects that is stored at the index + /// Gets or sets an array of <see cref="object"/> objects that is stored at the index /// of the Parameter. /// </summary> public object[] PositionalParameterValues @@ -190,6 +168,19 @@ set { _lockModes = value; } } + /// <summary> + /// Has the read-only/modifiable mode been explicitly set? + /// </summary> + /// <value> + /// <c>true</c>, the read-only/modifiable mode was explicitly set; <c>false</c>, the read-only/modifiable mode was not explicitly set + /// </value> + /// <seealso cref="ReadOnly" /> + /// <seealso cref="IsReadOnly(ISessionImplementor)" /> + public bool IsReadOnlyInitialized + { + get { return _isReadOnlyInitialized; } + } + private void CreatePositionalParameterLocations(ISessionFactoryImplementor factory) { _positionalParameterLocations = new int[_positionalParameterTypes.Length]; @@ -211,7 +202,6 @@ return array.Length; } - /// <summary></summary> public void LogParameters(ISessionFactoryImplementor factory) { var print = new Printer(factory); @@ -248,7 +238,7 @@ /// Ensure the Types and Values are the same length. /// </summary> /// <exception cref="QueryException"> - /// If the Lengths of <see cref="PositionalParameterTypes"/> and + /// If the Lengths of <see cref="PositionalParameterTypes"/> and /// <see cref="PositionalParameterValues"/> are not equal. /// </exception> public void ValidateParameters() @@ -290,11 +280,30 @@ } public bool Callable { get; set; } - + + /// <summary> + /// Should entities and proxies loaded by the query be put in read-only mode? + /// </summary> + /// <remarks> + /// The read-only/modifiable setting has no impact on entities/proxies returned by the + /// query that existed in the session before the query was executed. + /// </remarks> + /// <seealso cref="IsReadOnlyInitialized" /> + /// <seealso cref="IsReadOnly(ISessionImplementor)" /> public bool ReadOnly { - get { return _readOnly; } - set { _readOnly = value; } + get + { + if (!_isReadOnlyInitialized) + throw new InvalidOperationException("cannot call ReadOnly when IsReadOnlyInitialized returns false"); + + return _readOnly; + } + set + { + _readOnly = value; + _isReadOnlyInitialized = true; + } } /************** Filters ********************************/ @@ -674,7 +683,7 @@ public QueryParameters CreateCopyUsing(RowSelection selection) { var copy = new QueryParameters(_positionalParameterTypes, _positionalParameterValues, _namedParameters, _lockModes, - selection, _readOnly, _cacheable, _cacheRegion, _comment, _collectionKeys, + selection, _isReadOnlyInitialized, _readOnly, _cacheable, _cacheRegion, _comment, _collectionKeys, _optionalObject, _optionalEntityName, _optionalId, _resultTransformer); copy._positionalParameterLocations = _positionalParameterLocations; copy.processedSQL = processedSQL; @@ -683,5 +692,27 @@ copy.filteredParameterLocations = filteredParameterLocations; return copy; } + + /// <summary> + /// Should entities and proxies loaded by the query be put in read-only mode? If the + /// read-only/modifiable setting was not initialized + /// (i.e. <see cref="IsReadOnlyInitialized" /> == false), then the default + /// read-only/modifiable setting for the persistence context is returned instead. + /// </summary> + /// <remarks> + /// The read-only/modifiable setting has no impact on entities/proxies returned by the + /// query that existed in the session before the query was executed. + /// </remarks> + /// <seealso cref="IsReadOnlyInitialized" /> + /// <seealso cref="ReadOnly" /> + /// <seealso cref="IPersistenceContext.DefaultReadOnly" /> + /// <param name="session"></param> + /// <returns> + /// <c>true</c>, entities and proxies loaded by the query will be put in read-only mode; <c>false</c>, entities and proxies loaded by the query will be put in modifiable mode + /// </returns> + public bool IsReadOnly(ISessionImplementor session) + { + return _isReadOnlyInitialized ? this.ReadOnly : session.PersistenceContext.DefaultReadOnly; + } } } \ No newline at end of file Modified: branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/StatefulPersistenceContext.cs =================================================================== --- branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/StatefulPersistenceContext.cs 2011-02-11 14:44:33 UTC (rev 5379) +++ branches/ReadOnlyEntities/nhibernate/src/NHibernate/Engine/StatefulPersistenceContext.cs 2011-02-11 16:07:38 UTC (rev 5380) @@ -17,10 +17,10 @@ namespace NHibernate.Engine { - /// <summary> + /// <summary> /// A <see cref="IPersistenceContext"/> represents the state of persistent "stuff" which /// NHibernate is tracking. This includes persistent entities, collections, - /// as well as proxies generated. + /// as well as proxies generated. /// </summary> /// <remarks> /// There is meant to be a one-to-one correspondence between a SessionImpl and @@ -97,6 +97,8 @@ [NonSerialized] private BatchFetchQueue batchFetchQueue; + private bool defaultReadOnly; + /// <summary> Constructs a PersistentContext, bound to the given session. </summary> /// <param name="session">The session "owning" this context. </param> public StatefulPersistenceContext(ISessionImplementor session) @@ -132,15 +134,15 @@ get { return false; } } - /// <summary> - /// Get the session to which this persistence context is bound. + /// <summary> + /// Get the session to which this persistence context is bound. /// </summary> public ISessionImplementor Session { get { return session; } } - /// <summary> + /// <summary> /// Retrieve this persistence context's managed load context. /// </summary> public LoadContexts LoadContexts @@ -154,7 +156,7 @@ } } - /// <summary> + /// <summary> /// Get the <tt>BatchFetchQueue</tt>, instantiating one if necessary. /// </summary> public BatchFetchQueue BatchFetchQueue @@ -221,7 +223,7 @@ unownedCollections[key] = collection; } - /// <summary> + /// <summary> /// Get and remove a collection whose owner is not yet loaded, /// when its owner is being loaded /// </summary> @@ -245,7 +247,8 @@ { foreach (INHibernateProxy proxy in proxiesByKey.Values) { - proxy.HibernateLazyInitializer.Session = null; + ILazyInitializer li = proxy.HibernateLazyInitializer; + li.UnsetSession(); } ICollection collectionEntryArray = IdentityMap.ConcurrentEntries(collectionEntries); @@ -283,6 +286,13 @@ { get { return hasNonReadOnlyEntities; } } + + /// <inheritdoc /> + public bool DefaultReadOnly + { + get { return defaultReadOnly; } + set { defaultReadOnly = value; } + } private void SetHasNonReadOnlyEnties(Status value) { @@ -307,9 +317,9 @@ entityEntry.LockMode = LockMode.None; } - /// <summary> + /// <summary> /// Get the current state of the entity as known to the underlying - /// database, or null if there is no corresponding row + /// database, or null if there is no corresponding row /// </summary> public object[] GetDatabaseSnapshot(object id, IEntityPersister persister) { @@ -327,7 +337,7 @@ } } - /// <summary> + /// <summary> /// Retrieve the cached database snapshot for the requested entity key. /// </summary> /// <param name="key">The entity key for which to retrieve the cached snapshot </param> @@ -352,9 +362,9 @@ return (object[])snapshot; } - /// <summary> - /// Get the values of the natural id fields as known to the underlying - /// database, or null if the entity has no natural id or there is no + /// <summary> + /// Get the values of the natural id fields as known to the underlying + /// database, or null if the entity has no natural id or there is no /// corresponding row. /// </summary> public object[] GetNaturalIdSnapshot(object id, IEntityPersister persister) @@ -408,7 +418,7 @@ BatchFetchQueue.RemoveBatchLoadableEntityKey(key); } - /// <summary> + /// <summary> /// Get the entity instance associated with the given <tt>EntityKey</tt> /// </summary> public object GetEntity(EntityKey key) @@ -424,7 +434,7 @@ return entitiesByKey.ContainsKey(key); } - /// <summary> + /// <summary> /// Remove an entity from the session cache, also clear /// up other state associated with the entity, all except /// for the <tt>EntityEntry</tt> @@ -465,8 +475,8 @@ entitiesByUniqueKey[euk] = entity; } - /// <summary> - /// Retrieve the EntityEntry representation of the given entity. + /// <summary> + /// Retrieve the EntityEntry representation of the given entity. /// </summary> /// <param name="entity">The entity for which to locate the EntityEntry. </param> /// <returns> The EntityEntry for the given entity. </returns> @@ -505,8 +515,8 @@ return AddEntry(entity, status, loadedState, null, entityKey.Identifier, version, lockMode, existsInDatabase, persister, disableVersionIncrement, lazyPropertiesAreUnfetched); } - /// <summary> - /// Generates an appropriate EntityEntry instance and adds it + /// <summary> + /// Generates an appropriate EntityEntry instance and adds it /// to the event source's internal caches. /// </summary> public EntityEntry AddEntry(object entity, Status status, object[] loadedState, object rowId, object id, @@ -534,8 +544,8 @@ return proxiesByKey.ContainsValue(proxy); } - /// <summary> - /// Takes the given object and, if it represents a proxy, reassociates it with this event source. + /// <summary> + /// Takes the given object and, if it represents a proxy, reassociates it with this event source. /// </summary> /// <param name="value">The possible proxy to be reassociated. </param> /// <returns> Whether the passed value represented an actual proxy which got initialized. </returns> @@ -560,9 +570,9 @@ } } - /// <summary> + /// <summary> /// If a deleted entity instance is re-saved, and it has a proxy, we need to - /// reset the identifier of the proxy + /// reset the identifier of the proxy /// </summary> public void ReassociateProxy(object value, object id) { @@ -585,7 +595,7 @@ } } - /// <summary> + /// <summary> /// Associate a proxy that was instantiated by another session with this session /// </summary> /// <param name="li">The proxy initializer. </param> @@ -605,7 +615,7 @@ } } - /// <summary> + /// <summary> /// Get the entity instance underlying the given proxy, throwing /// an exception if the proxy is uninitialized. If the given object /// is not a proxy, simply return the argument. @@ -626,7 +636,7 @@ if (li.IsUninitialized) throw new PersistentObjectException("object was an uninitialized proxy for " + li.PersistentClass.FullName); - return li.GetImplementation(); // unwrap the object + return li.GetImplementation(); // unwrap the object } else { @@ -634,8 +644,8 @@ } } - /// <summary> - /// Possibly unproxy the given reference and reassociate it with the current session. + /// <summary> + /// Possibly unproxy the given reference and reassociate it with the current session. /// </summary> /// <param name="maybeProxy">The reference to be unproxied if it currently represents a proxy. </param> /// <returns> The unproxied instance. </returns> @@ -652,12 +662,12 @@ { ILazyInitializer li = proxy.HibernateLazyInitializer; ReassociateProxy(li, proxy); - return li.GetImplementation(); //initialize + unwrap the object + return li.GetImplementation(); //initialize + unwrap the object } return maybeProxy; } - /// <summary> + /// <summary> /// Attempts to check whether the given key represents an entity already loaded within the /// current session. /// </summary> @@ -676,11 +686,11 @@ } } - /// <summary> + /// <summary> /// If the existing proxy is insufficiently "narrow" (derived), instantiate a new proxy /// and overwrite the registration of the old one. This breaks == and occurs only for /// "class" proxies rather than "interface" proxies. Also init the proxy to point to - /// the given target implementation if necessary. + /// the given target implementation if necessary. /// </summary> /// <param name="proxy">The proxy instance to be narrowed. </param> /// <param name="persister">The persister for the proxied entity. </param> @@ -706,7 +716,13 @@ else { proxy = (INHibernateProxy)persister.CreateProxy(key.Identifier, session); + INHibernateProxy proxyOrig = proxiesByKey[key]; proxiesByKey[key] = proxy; //overwrite old proxy + if (proxyOrig != null) + { + bool readOnlyOrig = proxyOrig.HibernateLazyInitializer.ReadOnly; + proxy.HibernateLazyInitializer.ReadOnly = readOnlyOrig; + } return proxy; } } @@ -720,7 +736,7 @@ } } - /// <summary> + /// <summary> /// Return the existing proxy associated with the given <tt>EntityKey</tt>, or the /// third argument (the entity associated with the key) if no proxy exists. Init /// the proxy to the target implementation, if necessary. @@ -741,7 +757,7 @@ } } - /// <summary> + /// <summary> /// Return the existing proxy associated with the given <tt>EntityKey</tt>, or the /// argument (the entity associated with the key) if no proxy exists. /// (slower than the form above) @@ -761,7 +777,7 @@ /// <summary> Get the entity that owned this persistent collection when it was loaded </summary> /// <param name="collection">The persistent collection </param> - /// <returns> + /// <returns> /// The owner, if its entity ID is available from the collection's loaded key /// and the owner entity is in the persistence context; otherwise, returns null /// </returns> @@ -819,7 +835,7 @@ AddCollection(collection, ce, collection.Key); } - /// <summary> + /// <summary> /// Add a new collection (ie. a newly created one, just instantiated by the /// application, with no database state or snapshot) /// </summary> @@ -865,7 +881,7 @@ collectionEntries[collection] = ce; } - /// <summary> + /// <summary> /// add an (initialized) collection that was created by another session and passed /// into update() (ie. one with a snapshot and existing state on the database) /// </summary> @@ -903,7 +919,7 @@ return null; } - /// <summary> + /// <summary> /// Register a collection for non-lazy loading at the end of the two-phase load /// </summary> public void AddNonLazyCollection(IPersistentCollection collection) @@ -911,7 +927,7 @@ nonlazyCollections.Add(collection); } - /// <summary> + /// <summary> /// Force initialization of all non-lazy collections encountered during /// the current two-phase load (actually, this is a no-op, unless this /// is the "outermost" load) @@ -953,7 +969,7 @@ } /// <summary> Register a <tt>PersistentCollection</tt> object for an array. - /// Associates a holder with an array - MUST be called after loading + /// Associates a holder with an array - MUST be called after loading /// array, since the array instance is not created until endLoad(). /// </summary> public void AddCollectionHolder(IPersistentCollection holder) @@ -962,7 +978,7 @@ arrayHolders[holder.GetValue()] = holder; } - /// <summary> + /// <summary> /// Remove the mapping of collection to holder during eviction of the owning entity /// </summary> public IPersistentCollection RemoveCollectionHolder(object array) @@ -978,7 +994,7 @@ return GetCollectionEntry(coll).Snapshot; } - /// <summary> + /// <summary> /// Get the collection entry for a collection passed to filter, /// which might be a collection wrapper, an array, or an unwrapped /// collection. Return null if there is no entry. @@ -1061,7 +1077,7 @@ loadCounter--; } - /// <summary> + /// <summary> /// Search the persistence context for an owner for the child object, /// given a collection role /// </summary> @@ -1071,7 +1087,7 @@ // TODO persistent context (BackrefPropertyAccessor) } - /// <summary> + /// <summary> /// Search the persistence context for an index of the child object, given a collection role /// </summary> public object GetIndexInOwner(string entity, string property, object childObject, IDictionary mergeMap) @@ -1080,7 +1096,7 @@ // TODO persistent context (IndexPropertyAccessor) } - /// <summary> + /// <summary> /// Record the fact that the association belonging to the keyed entity is null. /// </summary> public void AddNullProperty(EntityKey ownerKey, string propertyName) @@ -1094,18 +1110,80 @@ return nullAssociations.Contains(new AssociationKey(ownerKey, propertyName)); } - /// <summary> Set the object to read only and discard it's snapshot</summary> - public void SetReadOnly(object entity, bool readOnly) + public void SetReadOnly(object entityOrProxy, bool readOnly) { + if (entityOrProxy == null) + throw new ArgumentNullException("entityOrProxy"); + + if (IsReadOnly(entityOrProxy) == readOnly) + return; + + if (entityOrProxy is INHibernateProxy) + { + INHibernateProxy proxy = (INHibernateProxy)entityOrProxy; + SetProxyReadOnly(proxy, readOnly); + if (NHibernateUtil.IsInitialized(proxy)) + { + SetEntityReadOnly(proxy.HibernateLazyInitializer.GetImplementation(), readOnly); + } + } + else + { + SetEntityReadOnly(entityOrProxy, readOnly); + + // PersistenceContext.proxyFor( entity ) returns entity if there is no proxy for that entity + // so need to check the return value to be sure it is really a proxy + object maybeProxy = this.Session.PersistenceContext.ProxyFor(entityOrProxy); + if (maybeProxy is INHibernateProxy ) + { + SetProxyReadOnly((INHibernateProxy)maybeProxy, readOnly); + } + } + } + + private void SetProxyReadOnly(INHibernateProxy proxy, bool readOnly) + { + if (proxy.HibernateLazyInitializer.Session != this.Session) + { + throw new AssertionFailure("Attempt to set a proxy to read-only that is associated with a different session"); + } + proxy.HibernateLazyInitializer.ReadOnly = readOnly; + } + + private void SetEntityReadOnly(object entity, bool readOnly) + { EntityEntry entry = GetEntry(entity); if (entry == null) { - throw new TransientObjectException("Instance of" + entity.GetType() + " was not associated with the session"); + throw new TransientObjectException("Instance was not associated with this persistence context"); } entry.SetReadOnly(readOnly, entity); hasNonReadOnlyEntities |= !readOnly; } - + + public bool IsReadOnly(object entityOrProxy) + { + if (entityOrProxy == null) + { + throw new AssertionFailure("object must be non-null."); + } + bool isReadOnly; + if (entityOrProxy is INHibernateProxy) + { + isReadOnly = ((INHibernateProxy)entityOrProxy).HibernateLazyInitia... [truncated message content] |