Nhibernate: Как найти ответственное поле для исключения переполнения SqlDateTime - PullRequest
7 голосов
/ 01 февраля 2011

Я знаю причину исключения (переполнение SqlDateTime. Должно быть между 01.01.1753 12:00:00 и 31.12.9999 11:59:59 PM.) - это необнуляемое поле DateTime вСущность и поэтому Nhibernate хочет сохранить меньшее значение DateTime, чем принимает MSSQL.

Проблема заключается в том, что в проекте слишком много объектов, чтобы найти правильное поле DateTime.

Возникает исключениепосле SaveOrUpdate (), но не запускается объектом, который я хочу сохранить, но любым другим объектом, который был загружен в текущем сеансе и на который теперь влияет flush ().

Как узнать, какое поледействительно отвечает за исключение?

Ответы [ 3 ]

3 голосов
/ 07 сентября 2011

Если вы приведете исключение к SqlTypeException, это предоставит доступ к сбору данных. Обычно в коллекции есть один ключ и одно значение. Значение - это SQL, который пытался выполнить. Изучив DML, вы сможете увидеть, по какой таблице действовали. Надеемся, что эта таблица достаточно узкая, чтобы сделать определение ошибочного столбца тривиальным.

Вот некоторый простой код, который я использую, чтобы выплевывать Ключ и Значение исключения.

            catch (SqlTypeException e)
            {
                foreach(var key in e.Data.Keys)
                {
                    System.Console.Write("Key is " + key.ToString());
                }
                foreach(var value in e.Data.Values)
                {
                    Console.WriteLine("Value is "+value.ToString());
                }
            }
0 голосов
/ 04 июля 2017

Вы можете создать класс, который реализует 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.

Счастливое кодирование!

0 голосов
/ 01 февраля 2011

Вы пытались заставить NHib вывести сгенерированный sql и проверить его для мошеннического DateTime? Было бы проще, если бы вы использовали что-то вроде NHProfiler (я не работаю на них, просто довольный клиент), но на самом деле все, что вам нужно, это показывать / изолировать sql в любом случае, который вы можно сделать из окна вывода с небольшим дополнительным усилием. Хитрость будет в том, что если это действительно глубокое сохранение, тогда потенциально может быть много sql для прочтения, но есть вероятность, что вы сможете обнаружить его довольно быстро.

...