7

I know the reason for the exception (SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.) is a non nullable DateTime field in a Entity and so Nhibernate wants to save a smaller DateTime value than MSSQL accepts.

The Problem ist that there are far to many entities in the project to find the right DateTime field.

The exception occurs after an SaveOrUpdate() but is not triggered by the entity i want to save but any other entity which was loaded in the current session and now is affected by the flush().

How can i find out which field really is responsible for the exception?

1
  • 1
    Please paste the SQL that gets sent to server from your app, you can use SQL profiler if you don't have nhprof or use log4net Commented Feb 1, 2011 at 18:42

3 Answers 3

3

If you cast the exception to a SqlTypeException, that will expose the Data collection. Normally there is a single Key and a single Value in the collection. The value is the SQL that was attempted to be executed. By examining the DML you can then see what table was being acted upon. Hopefully that table is narrow enough to make determining the offending column trivial.

Here's some simple code I use to spit out the Key and Value of the exception.

            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
  • 5
    In theory this works for me, but there doesn't seem to be any values in the Data Collection. Any other suggestions? Commented Apr 3, 2012 at 13:01
0

Have you tried forcing NHib to output the generated sql and reviewing that for the rogue DateTime? It'd be easier if you were using something like NHProfiler (I don't work for them, just a satisfied customer), but really all that's doing for you is showing/isolating the sql anyway, which you can do from the output window with a little extra effort. The trick will be if it's a really deep save, then there could potentially be a lot of sql to read through, but chances are you'll be able to spot it pretty quickly.

3
  • As SQL Profiler did not show any Query i guess that Nhibernate shuts the Application down before actually firing the Statement? The query i can find is only with "?" placeholders so i can not tell where the false value is set. Commented Feb 1, 2011 at 15:45
  • Bummer. Lemme take a look at a couple other options. Not to press the issue, but NHProf will show you those details. It might be worth pulling down the trial version. Configuration with your app is pretty simple. Commented Feb 1, 2011 at 15:50
  • Can you post the sql that you were able to locate, regardless of not seeing the param values? Commented Feb 1, 2011 at 15:51
0

You can create a class that implements both IPreUpdateEventListener and IPreInsertEventListener as follows:

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

You also need to then register this event handler when you configure the session factory. Add an instance to Configuration.EventListeners.PreUpdateEventListeners and to Configuration.EventListeners.PreInsertEventListeners and then use the Configuration object when creating NHibernate's session factory.

What this does is this: Every time NHibernate inserts or updates an entity it will call OnPreInsert() or OnPreUpdate() respectively. Each of these methods in turn calls CheckDateTimeWithinSqlRange().

CheckDateTimeWithinSqlRange() iterates over all property values of the entity, ie the object, that is being saved. If the property value is not null it then checks if it is of type DateTime. If that is the case it checks that it is not less than SqlDateTime.MinValue.Value (note the additional .Value to avoid exceptions). There is no need to check against SqlDateTime.MaxValue.Value if you are using SQL Server 2012 or later. They will happily accept even DateTime.MaxValue which is a few time ticks greater than SqlDateTime.MaxValue.Value.

If the value is outside of the allowed range this code will then throw an ArgumentOutOfRangeException with an appropriate message that includes the names of the class (entity) and property causing the problem as well as the actual value that was passed in. The message is similar to the equivalent SqlServerException for the SqlDateTime overflow exception but will make it easier to pinpoint the problem.

A couple of things to consider. Obviously this does not come for free. You will incur a runtime overhead as this logic consumes CPU. Depending on your scenario this may not be a problem. If it is, you can also consider optimizing the code given in this example to make it faster. One option could perhaps be to use caching to avoid the loop for the same class. Another option could be to use it only in test and development environments. For production you could then rely that the rest of the system operates correctly and the values will always be within valid range.

Also, be aware that this code introduces a dependency on SQL Server. NHibernate is typically used to avoid dependencies like this. Other database servers that are supported by NHibernate may have a different range of allowed values for datetime. Again, there are options for resolving this as well, e.g. by using different boundaries depending on SQL dialect.

Happy coding!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.