我知道异常的原因(SqlDateTime溢出。必须在1/1/1753 12:00:00 AM和12/31/9999 11:59:59 PM之间。)是一个不可为空的DateTime字段。实体等等Nhibernate希望保存比MSSQL接受的更小的DateTime值。
问题是项目中有很多实体要找到正确的DateTime字段。
异常发生在SaveOrUpdate()之后,但不是由我想保存的实体触发,而是在当前会话中加载的任何其他实体现在受flush()的影响。
我怎样才能找出哪个字段真正应对异常负责?
答案 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)
您可以创建一个实现IPreUpdateEventListener
和IPreInsertEventListener
的类,如下所示:
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.PreUpdateEventListeners
和Configuration.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方言使用不同的边界。
快乐的编码!