2011-02-01 41 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方言使用不同的边界。

快乐编码!