📄 defaultflushentityeventlistener.cs
字号:
using System;
using log4net;
using NHibernate.Action;
using NHibernate.Classic;
using NHibernate.Engine;
using NHibernate.Impl;
using NHibernate.Persister.Entity;
using NHibernate.Type;
using NHibernate.Util;
namespace NHibernate.Event.Default
{
/// <summary>
/// An event that occurs for each entity instance at flush time
/// </summary>
[Serializable]
public class DefaultFlushEntityEventListener : IFlushEntityEventListener
{
private static readonly ILog log = LogManager.GetLogger(typeof(DefaultFlushEntityEventListener));
/// <summary>
/// Flushes a single entity's state to the database, by scheduling an update action, if necessary
/// </summary>
public virtual void OnFlushEntity(FlushEntityEvent @event)
{
object entity = @event.Entity;
EntityEntry entry = @event.EntityEntry;
IEventSource session = @event.Session;
IEntityPersister persister = entry.Persister;
Status status = entry.Status;
EntityMode entityMode = session.EntityMode;
IType[] types = persister.PropertyTypes;
bool mightBeDirty = entry.RequiresDirtyCheck(entity);
object[] values = GetValues(entity, entry, entityMode, mightBeDirty, session);
@event.PropertyValues = values;
//TODO: avoid this for non-new instances where mightBeDirty==false
bool substitute = WrapCollections(session, persister, types, values);
if (IsUpdateNecessary(@event, mightBeDirty))
{
substitute = ScheduleUpdate(@event) || substitute;
}
if (status != Status.Deleted)
{
// now update the object .. has to be outside the main if block above (because of collections)
if (substitute)
persister.SetPropertyValues(entity, values, entityMode);
// Search for collections by reachability, updating their role.
// We don't want to touch collections reachable from a deleted object
if (persister.HasCollections)
{
new FlushVisitor(session, entity).ProcessEntityPropertyValues(values, types);
}
}
}
private object[] GetValues(object entity, EntityEntry entry, EntityMode entityMode, bool mightBeDirty, ISessionImplementor session)
{
object[] loadedState = entry.LoadedState;
Status status = entry.Status;
IEntityPersister persister = entry.Persister;
object[] values;
if (status == Status.Deleted)
{
//grab its state saved at deletion
values = entry.DeletedState;
}
else if (!mightBeDirty && loadedState != null)
{
values = loadedState;
}
else
{
CheckId(entity, persister, entry.Id, entityMode);
// grab its current state
values = persister.GetPropertyValues(entity, session.EntityMode);
CheckNaturalId(persister, entry.Id, values, loadedState, session);
}
return values;
}
/// <summary>
/// make sure user didn't mangle the id
/// </summary>
/// <param name="obj"></param>
/// <param name="persister"></param>
/// <param name="id"></param>
/// <param name="entityMode"></param>
public virtual void CheckId(object obj, IEntityPersister persister, object id, EntityMode entityMode)
{
if (id != null && id is DelayedPostInsertIdentifier)
{
// this is a situation where the entity id is assigned by a post-insert generator
// and was saved outside the transaction forcing it to be delayed
return;
}
if (persister.CanExtractIdOutOfEntity)
{
object oid = persister.GetIdentifier(obj, entityMode);
if (id == null)
{
throw new AssertionFailure("null id in " + persister.EntityName + " entry (don't flush the Session after an exception occurs)");
}
if (!persister.IdentifierType.IsEqual(id, oid, EntityMode.Poco))
{
throw new HibernateException("identifier of an instance of " + persister.EntityName + " was altered from " + id + " to " + oid);
}
}
}
private void CheckNaturalId(IEntityPersister persister, object identifier, object[] current, object[] loaded, ISessionImplementor session)
{
// TODO NH: Natural Identifier
//if (persister.HasNaturalIdentifier)
//{
// if (loaded == null)
// {
// loaded = session.PersistenceContext.GetNaturalIdSnapshot(identifier, persister);
// }
// IType[] types = persister.PropertyTypes;
// int[] props = persister.NaturalIdentifierProperties;
// bool[] updateable = persister.PropertyUpdateability;
// for (int i = 0; i < props.Length; i++)
// {
// int prop = props[i];
// if (!updateable[prop])
// {
// if (!types[prop].Equals(current[prop], loaded[prop]))
// {
// throw new HibernateException("immutable natural identifier of an instance of " + persister.EntityName + " was altered");
// }
// }
// }
//}
}
private bool WrapCollections(IEventSource session, IEntityPersister persister, IType[] types, object[] values)
{
if (persister.HasCollections)
{
// wrap up any new collections directly referenced by the object
// or its components
// NOTE: we need to do the wrap here even if its not "dirty",
// because collections need wrapping but changes to _them_
// don't dirty the container. Also, for versioned data, we
// need to wrap before calling searchForDirtyCollections
WrapVisitor visitor = new WrapVisitor(session);
// substitutes into values by side-effect
visitor.ProcessEntityPropertyValues(values, types);
return visitor.SubstitutionRequired;
}
else
{
return false;
}
}
private bool IsUpdateNecessary(FlushEntityEvent @event, bool mightBeDirty)
{
Status status = @event.EntityEntry.Status;
if (mightBeDirty || status == Status.Deleted)
{
// compare to cached state (ignoring collections unless versioned)
DirtyCheck(@event);
if (IsUpdateNecessary(@event))
{
return true;
}
else
{
// TODO H3.2 Different behaviour
//FieldInterceptionHelper.clearDirty(@event.Entity);
return false;
}
}
else
{
return HasDirtyCollections(@event, @event.EntityEntry.Persister, status);
}
}
private bool ScheduleUpdate(FlushEntityEvent @event)
{
EntityEntry entry = @event.EntityEntry;
IEventSource session = @event.Session;
EntityMode entityMode = session.EntityMode;
object entity = @event.Entity;
Status status = entry.Status;
IEntityPersister persister = entry.Persister;
object[] values = @event.PropertyValues;
if (log.IsDebugEnabled)
{
if (status == Status.Deleted)
{
log.Debug("Updating deleted entity: " + MessageHelper.InfoString(persister, entry.Id, session.Factory));
}
else
{
log.Debug("Updating entity: " + MessageHelper.InfoString(persister, entry.Id, session.Factory));
}
}
bool intercepted;
if (!entry.IsBeingReplicated)
{
// give the Interceptor a chance to process property values, if the properties
// were modified by the Interceptor, we need to set them back to the object
intercepted = HandleInterception(@event);
}
else
{
intercepted = false;
}
Validate(entity, persister, status, entityMode);
// increment the version number (if necessary)
object nextVersion = GetNextVersion(@event);
// if it was dirtied by a collection only
int[] dirtyProperties = @event.DirtyProperties;
if (@event.DirtyCheckPossible && dirtyProperties == null)
{
if (!intercepted && !@event.HasDirtyCollection)
{
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -