2011-02-01 2 views
7

예외 이유 (SqlDateTime 오버플로가 발생했습니다.)는 1/1/1753 12:00:00 AM과 12/31/9999 11:59:59 사이 여야합니다. PM.)는 엔터티의 null이 아닌 DateTime 필드이므로 Nhibernate는 MSSQL이 허용하는 것보다 작은 DateTime 값을 저장하려고합니다.Nhibernate : SqlDateTime 오버플로 예외에 대한 책임 필드를 찾는 방법

문제는 올바른 DateTime 필드를 찾기 위해 프로젝트에서 많은 엔터티가 있다는 것입니다.

SaveOrUpdate() 이후 예외가 발생하지만 저장하려고하는 엔티티에 의해 트리거되지 않지만 현재 세션에로드되어 현재 flush()의 영향을받는 다른 엔티티에 의해 트리거됩니다.

실제로 어떤 필드가 예외를 담당하는지 어떻게 알 수 있습니까?

+1

nhprof가 없거나 log4net이 없으면 SQL 프로파일 러를 사용할 수 있습니다. – Rippo

답변

0

당신은 생성 된 SQL을 출력하고 불량 DateTime을 검토하기 위해 NHib를 강제로 시도 했습니까? NHProfiler과 같은 것을 사용하면 더 쉬울 것입니다. (나는 만족스런 고객을 위해 작동하지 않습니다.)하지만 실제로는 출력 창에서 SQL을 표시/분리하고 있습니다. 약간의 노력으로 트릭은 정말 깊은 저장인데도 읽을 수있는 SQL이 많이있을 수 있지만 기회는 꽤 빨리 발견 할 수 있습니다.

+0

SQL 프로파일 러가 쿼리를 표시하지 않았으므로 Nhibernate가 실제로 응용 프로그램을 종료했다고 생각합니다 성명서 발포? 내가 찾을 수있는 쿼리는 "?" placeholder 그래서 내가 거짓 값이 설정되어 말할 수 없습니다. – Tardis

+0

Bummer. Lemme는 몇 가지 다른 옵션을 살펴 봅니다. 이슈를 눌러서는 안되지만 NHProf가 그 세부 사항을 보여줄 것입니다. 평가판을 내려받을 가치가 있습니다. 앱 구성은 매우 간단합니다. – nkirkes

+0

param 값이 보이지 않더라도 관계없이 찾을 수있는 SQL을 게시 할 수 있습니까? – nkirkes

3

예외를 SqlTypeException에 캐스팅하면 Data 컬렉션이 노출됩니다. 일반적으로 컬렉션에는 단일 Key와 단일 Value가 있습니다. 값은 실행하려고 시도한 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()); 
       } 
      } 
+2

NH 3.1에서는 나에게 적합하지 않았습니다. –

+5

이론 상으로는 이것이 저에게는 효과적이지만 데이터 수집에는 값이없는 것 같습니다. 다른 제안? – Aries51

0

다음과 같이 모두 IPreUpdateEventListenerIPreInsertEventListener를 구현하는 클래스를 만들 수 있습니다

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.PreUpdateEventListenersConfiguration.EventListeners.PreInsertEventListeners에 인스턴스를 추가 한 다음 NHibernate의 세션 팩토리를 만들 때 Configuration 개체를 사용하십시오.

이것은 무엇을 하는가 : NHibernate가 엔티티를 삽입하거나 업데이트 할 때마다 각각 OnPreInsert() 또는 OnPreUpdate()을 호출 할 것이다. 이 방법들 각각은 CheckDateTimeWithinSqlRange()을 호출합니다.

CheckDateTimeWithinSqlRange()은 개체, 즉 저장되는 개체의 모든 속성 값을 반복합니다. 등록 정보 값이 널이 아닌 경우, 유형이 DateTime인지 점검합니다. 이 경우 예외가 발생하지 않도록 이상인지 확인합니다 (예외 발생을 방지하려면 추가 .Value에주의하십시오). SQL Server 2012 이상을 사용하는 경우 SqlDateTime.MaxValue.Value을 확인할 필요가 없습니다. 그들은 심지어 SqlDateTime.MaxValue.Value보다 약간 시간 틱 인 DateTime.MaxValue조차도 기꺼이 받아 들일 것입니다.

값이 허용 범위를 벗어나는 경우이 코드는 전달 된 실제 값뿐만 아니라 문제를 일으키는 클래스 (엔티티) 및 속성의 이름을 포함하는 적절한 메시지와 함께 ArgumentOutOfRangeException을 던집니다. 이 메시지는 SqlDateTime 오버플로 예외에 해당하는 SqlServerException과 비슷하지만 문제를 쉽게 찾아 낼 수 있습니다.

몇 가지 고려해야 할 사항이 있습니다. 분명히 이것은 자유롭지 않습니다. 이 로직이 CPU를 소비하므로 런타임 오버 헤드가 발생합니다. 시나리오에 따라 문제가되지 않을 수도 있습니다. 그렇다면이 예제에 제공된 코드를 최적화하여 더 빠르게 만들 것을 고려할 수도 있습니다. 한 옵션은 같은 클래스에 대한 루프를 피하기 위해 캐싱을 사용하는 것일 수 있습니다. 또 다른 옵션은 테스트 및 개발 환경에서만이 옵션을 사용할 수 있습니다. 생산을 위해서는 나머지 시스템이 올바르게 작동하고 값이 항상 유효 범위 내에 있음을 의지 할 수 있습니다.

또한이 코드는 SQL Server에 대한 종속성을 소개합니다. Hibernate는 일반적으로 이와 같은 의존성을 피하기 위해 사용된다. Hibernate에서 지원하는 다른 데이터베이스 서버는 datetime에 대해 허용되는 값의 범위가 다를 수 있습니다. 다시 말하지만이 문제를 해결할 수있는 옵션이 있습니다. SQL 방언에 따라 다른 경계를 사용합니다.

해피 코딩!

관련 문제