Nhibernate: как найти ответственное поле для исключения переполнения SqlDateTime

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

Проблема в том, что в проекте очень много сущностей, чтобы найти правильное поле DateTime.

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

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


person Tardis    schedule 01.02.2011    source источник
comment
Вставьте SQL, который отправляется на сервер из вашего приложения, вы можете использовать профилировщик SQL, если у вас нет nhprof, или использовать log4net   -  person Rippo    schedule 01.02.2011


Ответы (3)


Если вы приведете исключение к 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());
                }
            }
person Bill Gregg    schedule 07.09.2011
comment
Теоретически это работает для меня, но, похоже, в сборе данных нет никаких значений. Есть другие предложения? - person Aries51; 03.04.2012

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

person nkirkes    schedule 01.02.2011
comment
Поскольку SQL Profiler не отображал никаких запросов, я предполагаю, что Nhibernate закрывает приложение перед фактическим запуском оператора? Я могу найти запрос только с? заполнители, поэтому я не могу сказать, где установлено ложное значение. - person Tardis; 01.02.2011
comment
Облом. Дайте мне взглянуть на пару других вариантов. Не для того, чтобы настаивать на проблеме, но NHProf покажет вам эти детали. Возможно, стоит скинуть пробную версию. Конфигурация с вашим приложением довольно проста. - person nkirkes; 01.02.2011
comment
Можете ли вы опубликовать sql, который вам удалось найти, независимо от того, не видите ли значения параметров? - person nkirkes; 01.02.2011

Вы можете создать класс, который реализует как 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, могут иметь другой диапазон допустимых значений для datetime. Опять же, есть варианты решения этой проблемы, например используя разные границы в зависимости от диалекта SQL.

Удачного кодирования!

person Manfred    schedule 04.07.2017