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

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

问题在于项目中有很多实体找不到合适的DateTime字段。

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

我怎样才能找出哪个领域真的是负责例外?


如果您将异常强制转换为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());
                }
            }

你有没有试过迫使NHib输出生成的sql并为流氓DateTime检查? 如果你使用的是NHProfiler(我不为他们工作,只是一个满意的客户),那会更容易一些,但是真正为你做的一切就是显示/隔离sql,你可以从输出窗口稍加额外的努力。 诀窍是如果这是一个非常深入的保存,那么可能会有很多sql需要通读,但很可能你很快就会发现它。


您可以创建一个实现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类型。 如果是这种情况,它会检查它是否不小于SqlDateTime.MinValue.Value (请注意附加的.Value以避免异常)。 如果您使用SQL Server 2012或更高版本,则无需检查SqlDateTime.MaxValue.Value 。 他们会高兴地接受即使是比SqlDateTime.MaxValue.ValueSqlDateTime.MaxValue.Value DateTime.MaxValue

如果该值超出允许范围,则此代码将引发一个ArgumentOutOfRangeException并带有适当的消息,其中包含导致该问题的类(实体)和属性的名称以及传入的实际值。消息是类似的到SqlDateTime溢出异常的等价SqlServerException ,但可以更容易查明问题。

几件事情要考虑。 显然这不是免费的。 由于此逻辑消耗CPU,因此会产生运行时间开销。 根据你的情况,这可能不成问题。 如果是这样,您还可以考虑优化此示例中给出的代码以使其更快。 一种选择可能是使用缓存来避免同一类的循环。 另一种选择可能是仅在测试和开发环境中使用它。 对于生产,您可以依靠系统的其他部分正常运行,并且这些值始终在有效范围内。

此外,请注意,此代码引入了对SQL Server的依赖关系。 NHibernate通常用于避免这样的依赖。 NHibernate支持的其他数据库服务器可能有不同的datetime允许值范围。 同样,还有解决这个问题的选项,例如根据SQL方言使用不同的边界。

快乐的编码!

链接地址: http://www.djcxy.com/p/4581.html

上一篇: Nhibernate: How to find responsible Field for SqlDateTime overflow exception

下一篇: Data structure and algorithm for representing/allocating free space in a file