2011-02-01 68 views
7

我知道異常的原因(SQLDATETIME溢出必須是1753年1月1日59:59 12/31/9999之間12:00:00 AM和PM。)是實體中的一個不可空的DateTime字段,因此Nhibernate想要保存比MSSQL接受的更小的DateTime值。NHibernate的:如何找到負責現場爲SQLDATETIME溢出異常

問題在於項目中有很多實體找不到合適的DateTime字段。

在SaveOrUpdate()後發生異常,但未由我想保存的實體觸發,但在當前會話中加載的任何其他實體現在受到flush()影響。

我怎樣才能找出哪個領域真的是負責例外?

+1

請粘貼從應用程序發送到服務器的SQL,如果您沒有nhprof或使用log4net,則可以使用SQL事件探查器 – Rippo 2011-02-01 18:42:05

回答

0

你有沒有試過迫使NHib輸出生成的SQL並審查該流氓DateTime?如果你使用像NHProfiler這樣的東西(我不爲他們工作,只是一個滿意的客戶),那會更容易些,但是真正爲你做的所有事情都是顯示/隔離sql,你可以從輸出窗口稍加額外的努力。訣竅是如果這是一個非常深入的保存,那麼可能會有很多sql需要通讀,但很可能你很快就會發現它。

+0

由於SQL事件探查器未顯示任何查詢,我猜測Nhibernate在實際應用之前關閉應用程序發表聲明?我能找到的查詢只有「?」佔位符,所以我不能告訴假設值設置在哪裏。 – Tardis 2011-02-01 15:45:48

+0

無賴。 Lemme看看其他一些選項。不要按這個問題,但NHProf會告訴你這些細節。這可能是值得拉下試用版。使用您的應用進行配置非常簡單。 – nkirkes 2011-02-01 15:50:23

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()); 
       } 
      } 
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}"); 
       } 
      } 
     } 
    } 
    } 

您還需要在配置會話工廠然後註冊此事件處理程序。在創建NHibernate的會話工廠時,將實例添加到Configuration.EventListeners.PreUpdateEventListenersConfiguration.EventListeners.PreInsertEventListeners,然後使用Configuration對象。

這是幹什麼的:每當NHibernate插入或更新實體時,它將分別調用OnPreInsert()OnPreUpdate()。每種方法都會調用CheckDateTimeWithinSqlRange()

CheckDateTimeWithinSqlRange()遍歷實體的所有屬性值,即對象,正被保存。如果屬性值不爲空,則會檢查它是否爲DateTime。如果是這種情況,它會檢查它是否不小於SqlDateTime.MinValue.Value(請注意額外的.Value以避免例外)。如果您使用的是SQL Server 2012或更高版本,則無需檢查SqlDateTime.MaxValue.Value。他們會高興地接受,即使是DateTime.MaxValue,這是幾次大於SqlDateTime.MaxValue.Value

如果該值超出允許的範圍,則此代碼將引發帶有適當消息的ArgumentOutOfRangeException,該消息包含導致問題的類(實體)和屬性的名稱以及傳入的實際值。該消息與SqlDateTime溢出異常的等效SqlServerException類似,但可以更容易查明問題。

一些事情要考慮。顯然這不是免費的。由於此邏輯消耗CPU,因此會產生運行時間開銷。根據你的情況,這可能不成問題。如果是這樣,您還可以考慮優化此示例中給出的代碼以使其更快。一種選擇可能是使用緩存來避免同一類的循環。另一種選擇可能是僅在測試和開發環境中使用它。對於生產,您可以依靠系統的其他部分正常運行,並且這些值始終在有效範圍內。

另外,請注意,此代碼引入了對SQL Server的依賴關係。 NHibernate通常用於避免這樣的依賴。 NHibernate支持的其他數據庫服務器可能有不同的datetime允許值範圍。同樣,還有解決這個問題的選項,例如通過根據SQL方言使用不同的邊界。

快樂編碼!