How to turn off NHibernate's automatic (dirty checking) update behaviour?

前端 未结 4 699
臣服心动
臣服心动 2020-12-15 03:40

I\'ve just discovered that if I get an object from an NHibernate session and change a property on object, NHibernate will automatically update the object on commit without m

相关标签:
4条回答
  • 2020-12-15 04:21

    Calling SaveOrUpdate() or Save() makes an object persistent. If you've retrieved it using an ISession or from a reference to a persistent object, then the object is persistent and flushing the session will save changes. You can prevent this behavior by calling Evict() on the object which makes it transient.

    Edited to add: I generally consider an ISession to be a unit of work. This is easily implemented in a web app. using session-per-request but requires more control in WinForms.

    0 讨论(0)
  • 2020-12-15 04:43

    We did this by using the Event Listeners with NH (This isn't my work - but I can't find the link for where I did it...).

    We have a EventListener for when reading in the data, to set it as ReadOnly - and then one for Save (and SaveOrUpdate) to set them as loaded, so that object will persist when we manually call Save() on it.

    That - or you could use an IStatelessSession which has no State/ChangeTracking.

    This sets the entity/item as ReadOnly immediately on loading.

    I've only included one Insertion event listener, but my config code references all of them.

    /// <summary>
    /// A listener that once an object is loaded will change it's status to ReadOnly so that
    /// it will not be automatically saved by NH
    /// </summary>
    /// <remarks>
    /// For this object to then be saved, the SaveUpdateEventListener is to be used.
    /// </remarks>
    public class PostLoadEventListener : IPostLoadEventListener
    {
        public void OnPostLoad(PostLoadEvent @event)
        {
            EntityEntry entry = @event.Session.PersistenceContext.GetEntry(@event.Entity);
    
            entry.BackSetStatus(Status.ReadOnly);
        }
    }
    

    On saving the object, we call this to set that object to Loaded (meaning it will now persist)

    public class SaveUpdateEventListener : ISaveOrUpdateEventListener
    {
        public static readonly CascadingAction ResetReadOnly = new ResetReadOnlyCascadeAction();
    
        /// <summary>
        /// Changes the status of any loaded item to ReadOnly.
        /// </summary>
        /// <remarks>
        /// Changes the status of all loaded entities, so that NH will no longer TrackChanges on them.
        /// </remarks>
        public void OnSaveOrUpdate(SaveOrUpdateEvent @event)
        {
            var session = @event.Session;
            EntityEntry entry = session.PersistenceContext.GetEntry(@event.Entity);
    
            if (entry != null && entry.Persister.IsMutable && entry.Status == Status.ReadOnly)
            {
                entry.BackSetStatus(Status.Loaded);
                CascadeOnUpdate(@event, entry.Persister, @event.Entry);
            }
        }
    
        private static void CascadeOnUpdate(SaveOrUpdateEvent @event, IEntityPersister entityPersister, 
            object entityEntry)
        {
            IEventSource source = @event.Session;
            source.PersistenceContext.IncrementCascadeLevel();
            try
            {
                new Cascade(ResetReadOnly, CascadePoint.BeforeFlush, source).CascadeOn(entityPersister, entityEntry);
            }
            finally
            {
                source.PersistenceContext.DecrementCascadeLevel();
            }
        }
    }
    

    And we implement it into NH thus so:

        public static ISessionFactory CreateSessionFactory(IPersistenceConfigurer dbConfig, Action<MappingConfiguration> mappingConfig, bool enabledChangeTracking,bool enabledAuditing, int queryTimeout)
        {
            return Fluently.Configure()
                .Database(dbConfig)
                .Mappings(mappingConfig)
                .Mappings(x => x.FluentMappings.AddFromAssemblyOf<__AuditEntity>())
                .ExposeConfiguration(x => Configure(x, enabledChangeTracking, enabledAuditing,queryTimeout))
                .BuildSessionFactory();
        }
    
        /// <summary>
        /// Configures the specified config.
        /// </summary>
        /// <param name="config">The config.</param>
        /// <param name="enableChangeTracking">if set to <c>true</c> [enable change tracking].</param>
        /// <param name="queryTimeOut">The query time out in minutes.</param>
        private static void Configure(NHibernate.Cfg.Configuration config, bool enableChangeTracking, bool enableAuditing, int queryTimeOut)
        {
            config.SetProperty(NHibernate.Cfg.Environment.Hbm2ddlKeyWords, "none");
            if (queryTimeOut > 0)
            {
                config.SetProperty("command_timeout", (TimeSpan.FromMinutes(queryTimeOut).TotalSeconds).ToString());
            }
    
            if (!enableChangeTracking)
            {
                config.AppendListeners(NHibernate.Event.ListenerType.PostLoad, new[] { new Enact.Core.DB.NHib.Listeners.PostLoadEventListener() });
                config.AppendListeners(NHibernate.Event.ListenerType.SaveUpdate, new[] { new Enact.Core.DB.NHib.Listeners.SaveUpdateEventListener() });
                config.AppendListeners(NHibernate.Event.ListenerType.PostUpdate, new[] { new Enact.Core.DB.NHib.Listeners.PostUpdateEventListener() });
                config.AppendListeners(NHibernate.Event.ListenerType.PostInsert, new[] { new Enact.Core.DB.NHib.Listeners.PostInsertEventListener() });
            }
        }
    
    0 讨论(0)
  • 2020-12-15 04:46

    My solution:

    1. In your initial ISession creation, (somewhere inside your injection framework registrations) set DefaultReadOnly to true.
    2. In your IRepository implementation which wraps around NHibernate and manages the ISession and such, in the Insert, Update, InsertUpdate and Delete (or similar) methods which call ISession.Save, Update, SaveUpdate, etc., call SetReadOnly for the entity and flag set to false.
    0 讨论(0)
  • 2020-12-15 04:47

    You can set Session.FlushMode to FlushMode.Never. This will make your operations explicit

    ie: on tx.Commit() or session.Flush(). Of course this will still update the database upon commit/flush. If you do not want this behavior, then call session.Evict(yourObj) and it will then become transient and NHibernate will not issue any db commands for it.

    Response to your edit: Yes, that guy gives you more options on how to control it.

    0 讨论(0)
提交回复
热议问题