Вы можете создать класс, который реализует IPreUpdateEventListener
и IPreInsertEventListener
следующим образом:
public class InsertUpdateListener : IPreInsertEventListener, IPreUpdateEventListener {
public bool OnPreInsert(PreInsertEvent @event) {
CheckDateTimeWithinSqlRange(@event.Persister, @event.State);
return false;
}
public bool OnPreUpdate(PreUpdateEvent @event) {
CheckDateTimeWithinSqlRange(@event.Persister, @event.State);
return false;
}
private static void CheckDateTimeWithinSqlRange(IEntityPersister persister, IReadOnlyList<object> state) {
var rgnMin = System.Data.SqlTypes.SqlDateTime.MinValue.Value;
// There is a small but relevant difference between DateTime.MaxValue and SqlDateTime.MaxValue.
// DateTime.MaxValue is bigger than SqlDateTime.MaxValue but still within the valid range of
// values for SQL Server. Therefore we test against DateTime.MaxValue and not against
// SqlDateTime.MaxValue. [Manfred, 04jul2017]
//var rgnMax = System.Data.SqlTypes.SqlDateTime.MaxValue.Value;
var rgnMax = DateTime.MaxValue;
for (var i = 0; i < state.Count; i++) {
if (state[i] != null
&& state[i] is DateTime) {
var value = (DateTime)state[i];
if (value < rgnMin /*|| value > rgnMax*/) { // we don't check max as SQL Server is happy with DateTime.MaxValue [Manfred, 04jul2017]
throw new ArgumentOutOfRangeException(persister.PropertyNames[i], value,
$"Property '{persister.PropertyNames[i]}' for class '{persister.EntityName}' must be between {rgnMin:s} and {rgnMax:s} but was {value:s}");
}
}
}
}
}
Вам также необходимо зарегистрировать этот обработчик событий при настройке фабрики сеансов.Добавьте экземпляр к Configuration.EventListeners.PreUpdateEventListeners
и Configuration.EventListeners.PreInsertEventListeners
, а затем используйте объект Configuration
при создании фабрики сеансов NHibernate.
Что это делает так: Каждый раз, когда NHibernate вставляет или обновляет объект, который он будет вызывать OnPreInsert()
или OnPreUpdate()
соответственно.Каждый из этих методов в свою очередь вызывает CheckDateTimeWithinSqlRange()
.
CheckDateTimeWithinSqlRange()
, перебирает все значения свойств объекта, то есть объекта, который сохраняется.Если значение свойства не равно нулю, оно проверяет, имеет ли оно тип DateTime
.Если это так, он проверяет, что оно не меньше SqlDateTime.MinValue.Value
(обратите внимание на дополнительный .Value
, чтобы избежать исключений).Нет необходимости проверять SqlDateTime.MaxValue.Value
, используете ли вы SQL Server 2012 или более позднюю версию.Они с радостью примут даже DateTime.MaxValue
, что в несколько раз больше, чем SqlDateTime.MaxValue.Value
.
Если значение выходит за допустимые пределы, этот код затем выдаст ArgumentOutOfRangeException
с соответствующим сообщением, которое включаетимена класса (сущности) и свойства, вызывающие проблему, а также фактическое значение, которое было передано. Сообщение аналогично SqlServerException
для исключения переполнения SqlDateTime, но упростит выявление проблемы.
Несколько вещей для рассмотрения.Очевидно, что это не бесплатно.Вы будете подвергаться накладным расходам во время выполнения, так как эта логика потребляет процессор.В зависимости от вашего сценария это может не быть проблемой.Если это так, вы можете также рассмотреть возможность оптимизации кода, приведенного в этом примере, чтобы сделать его быстрее.Возможно, одним из вариантов может быть использование кэширования, чтобы избежать цикла для того же класса.Другим вариантом может быть использование его только в средах тестирования и разработки.Для производства вы можете полагаться, что остальная часть системы работает правильно, и значения всегда будут в допустимых пределах.
Также помните, что этот код вводит зависимость от SQL Server.NHibernate обычно используется, чтобы избежать таких зависимостей.Другие серверы баз данных, которые поддерживаются NHibernate, могут иметь другой диапазон допустимых значений для даты и времени.Опять же, есть варианты решения этой проблемы, например, используя разные границы в зависимости от диалекта SQL.
Счастливое кодирование!