public sealed class ValidationEventListener :
RegistrableListener,
IFlushEntityEventListener,
ISaveOrUpdateEventListener,
IMergeEventListener
{
public Action<List<string>> ProcessValidationErrorsAs { get; set; }
public ValidationEventListener(Action<List<string>> processValidationErrorsAs)
{
ProcessValidationErrorsAs = processValidationErrorsAs;
}
/// <summary>
/// Entity was flushed.
/// Event is intersected, entity is being validated in case it supports validation
/// </summary>
/// <param name="event"></param>
public void OnFlushEntity(FlushEntityEvent @event)
{
//Don\'t update if the target state is deleted
if (@event.EntityEntry.Status == Status.Deleted)
return;
//Attempt casting the entity
IValidatable asValidatable = @event.Entity as IValidatable;
//Entity is not IValidatable
if (asValidatable == null)
return;
//Check for dirty properties
if (HasDirtyProperties(@event))
{
ProcessValidatableInternal(asValidatable);
}
//Hook in if this is a new entry
if (!@event.EntityEntry.ExistsInDatabase)
{
ExplicitInsertCall(asValidatable);
}
}
/// <summary>
/// Entity is being saved or updated. Executes explicit update
/// </summary>
/// <param name="event"></param>
public void OnSaveOrUpdate(SaveOrUpdateEvent @event)
{
ExplicitUpdateCall(@event.Entity as IValidatable);
}
/// <summary>
/// Entity is being merged. Executes explicit update
/// </summary>
/// <param name="event"></param>
public void OnMerge(MergeEvent @event)
{
//WARN @event.Entity is null
ExplicitUpdateCall(@event.Original as IValidatable);
}
/// <summary>
/// Entity is being merged. Executes explicit update
/// </summary>
/// <param name="event"></param>
/// <param name="copiedAlready"></param>
public void OnMerge(MergeEvent @event, IDictionary copiedAlready)
{
ExplicitUpdateCall(@event.Original as IValidatable);
}
/// <summary>
/// Explicit update call is being executed. Entity is being validated if it supports validation
/// </summary>
/// <param name="asTimeStampable"></param>
void ExplicitUpdateCall(IValidatable asValidatable)
{
//Entity is not IValidatable
if (asValidatable == null)
return;
ProcessValidatableInternal(asValidatable);
}
/// <summary>
/// Explicit insert call is being executed. Entity is being validated if it supports validation
/// </summary>
/// <param name="asTimeStampable"></param>
void ExplicitInsertCall(IValidatable asValidatable)
{
//Entity is not IValidatable
if (asValidatable == null)
return;
ProcessValidatableInternal(asValidatable);
}
/// <summary>
/// Processes the validatable entity
/// </summary>
/// <param name="asValidatable"></param>
void ProcessValidatableInternal(IValidatable asValidatable)
{
var result = asValidatable.Validator.ValidateBeforePersist(asValidatable);
if (!result.IsValid)
{
ProcessValidationErrorsAs(result);
}
}
/// <summary>
/// Determines whether the entity contains any dirty properties
/// </summary>
/// <param name="event"></param>
/// <returns></returns>
static bool HasDirtyProperties(FlushEntityEvent @event)
{
//Create shortcuts
ISessionImplementor session = @event.Session;
EntityEntry entry = @event.EntityEntry;
object entity = @event.Entity;
//Checks if the entity coming with explicit parameters not to check dirty properties,
//does not exists in database yet (dirty properties cannot be set)
//or not loaded (unloaded from session explicitly)
if (!entry.RequiresDirtyCheck(entity) || !entry.ExistsInDatabase || entry.LoadedState == null)
return false;
IEntityPersister persister = entry.Persister;
object[] currentState = persister.GetPropertyValues(entity, session.EntityMode);
object[] loadedState = entry.LoadedState;
//Checks for dirty properties
return persister.EntityMetamodel.Properties
.Where((property, i) =>
!LazyPropertyInitializer.UnfetchedProperty.Equals(currentState[i])
&& property.Type.IsDirty(loadedState[i], currentState[i], session)
)
.Any();
}
}