Nhibernate:如何为SqlDateTime溢出异常找到负责的字段

时间:2011-02-01 15:19:48

标签: sql-server nhibernate exception mapping entity

我知道异常的原因(SqlDateTime溢出。必须在1/1/1753 12:00:00 AM和12/31/9999 11:59:59 PM之间。)是一个不可为空的DateTime字段。实体等等Nhibernate希望保存比MSSQL接受的更小的DateTime值。

问题是项目中有很多实体要找到正确的DateTime字段。

异常发生在SaveOrUpdate()之后,但不是由我想保存的实体触发,而是在当前会话中加载的任何其他实体现在受flush()的影响。

我怎样才能找出哪个字段真正应对异常负责?

3 个答案:

答案 0 :(得分:3)

如果将异常强制转换为SqlTypeException,则会公开Data集合。通常,集合中只有一个Key和一个Value。该值是尝试执行的SQL。通过检查DML,您可以看到正在对哪个表进行操作。希望该表足够窄,以确定违规列是微不足道的。

这是我用来吐出异常的Key和Value的一些简单代码。

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

答案 1 :(得分:0)

您是否尝试过强制NHib输出生成的sql并查看流氓DateTime的内容?如果你使用像NHProfiler这样的东西会更容易(我不为他们工作,只是满意的客户),但真正为你做的只是显示/隔离sql,你可以做从输出窗口稍加省力。诀窍是如果它是一个非常深的保存,那么可能会有很多sql可以通读,但很可能你很快就能发现它。

答案 2 :(得分: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()遍历实体的所有属性值,即正在保存的对象。如果属性值不为null,则检查它是否为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方言使用不同的边界。

快乐的编码!