Skip to content

Avoid InvalidCastException with Oracle number high precision values #2641

Closed
@kriewall

Description

@kriewall

Describe the issue

I'm getting an InvalidCastException when rehydrating numbers with very small / high precision values in an Oracle dB. The limitation appears to actually be due to the fact that Oracle can represent numbers with higher precision than C# (described here); however my main question here is how to prevent NHibernate from inserting values into the dB that have higher precision than C# can handle. If you have recommendations how to work around the intake issue, that would be much appreciated as well.

Expected behavior

  • By default, C# precision limits should be respected when inserting doubles into the dB.
  • Ideally intake of high precision values would be handled gracefully; however, I recognize you may not have control over this since the issue occurs in the Oracle reader.

As a workaround to prevent overly precise data from getting inserted into the dB, do I need to specify precision and scale everywhere I map a double? Is there a generic way to do this?

Environment

  • Windows 10 and Windows 2016 server (observed on both)
  • NHibernate
    • Problematic data inserted into production dB under NHibernate 5.2.7
    • InvalidCastException occurs on both 5.2.7 and 5.3.5.
  • Oracle dB: 11.2.0.2
  • Oracle.ManagedDataAccess: 18.3, but have also tried with 19.10

Additional information

Stack trace:

 at Oracle.ManagedDataAccess.Client.OracleDataReader.GetDecimal(Int32 i)
       at Oracle.ManagedDataAccess.Client.OracleDataReader.GetValue(Int32 i)
       at Oracle.ManagedDataAccess.Client.OracleDataReader.get_Item(Int32 i)
       at NHibernate.Type.NullableType.NullSafeGet(DbDataReader rs, String name, ISessionImplementor session)
       at NHibernate.Type.NullableType.NullSafeGet(DbDataReader rs, String[] names, ISessionImplementor session, Object owner)
       at NHibernate.Type.AbstractType.Hydrate(DbDataReader rs, Object owner)
       at NHibernate.Persister.Entity.AbstractEntityPersister.Hydrate(DbDataReader rs, Object id, Object obj, String[][] suffixedPropertyColumns, ISet`1 fetchedLazyProperties, Boolean allProperties, Int32[] indexes, ISessionImplementor session)
       at NHibernate.Persister.Entity.LoadableExtensions.Hydrate(ILoadable loadable, DbDataReader rs, ISessionImplementor session)
       at NHibernate.Loader.Loader.LoadFromResultSet(DbDataReader rs, Int32 i, ILoadable persister, EntityKey key, LockMode lockMode, ILoadable rootPersister, ISessionImplementor session)
       at NHibernate.Loader.Loader.InstanceNotYetLoaded(DbDataReader dr, EntityKey optionalObjectKey, Object optionalObject, IList hydratedObjects, ISessionImplementor session)
       at NHibernate.Loader.Loader.GetRow(DbDataReader rs, ILoadable[] persisters, EntityKey[] keys, LockMode[] lockModes, Boolean mustLoadMissingEntity, Action`2 cacheBatchingHandler)
       at NHibernate.Loader.Loader.GetRowFromResultSet(DbDataReader resultSet, QueryParameters queryParameters, LockMode[] lockModeArray, Boolean returnProxies, IResultTransformer forcedResultTransformer, QueryCacheResultBuilder queryCacheResultBuilder, Action`2 cacheBatchingHandler)
       at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryCacheResultBuilder queryCacheResultBuilder)
       at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryCacheResultBuilder queryCacheResultBuilder)
       at NHibernate.Loader.Loader.LoadCollectionBatch(ISessionImplementor session, IType type)
at NHibernate.Loader.Loader.LoadCollectionBatch(ISessionImplementor session, Object[] ids, IType type)
       at NHibernate.Loader.Collection.BatchingCollectionInitializer.Initialize(Object id, ISessionImplementor session)
       at NHibernate.Persister.Collection.AbstractCollectionPersister.Initialize(Object key, ISessionImplementor session)
       at NHibernate.Event.Default.DefaultInitializeCollectionEventListener.OnInitializeCollection(InitializeCollectionEvent event)
       at NHibernate.Impl.SessionImpl.InitializeCollection(IPersistentCollection collection, Boolean writing)
       at NHibernate.Collection.AbstractPersistentCollection.Initialize(Boolean writing)
       at NHibernate.Collection.AbstractPersistentCollection.Read()
       at NHibernate.Collection.Generic.PersistentGenericBag`1.GetEnumerator()
       at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
       at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
       at MathNet.Numerics.Statistics.DescriptiveStatistics.ComputeDecimal(IEnumerable`1 data)
       at MathNet.Numerics.Statistics.DescriptiveStatistics..ctor(IEnumerable`1 data, Boolean increasedAccuracy)
       at Locnes.Core.Domain.State.Smoothing.LotHistoryData.GetFinalCheckStdev() in C:\src\locnes\Locnes.Core\Domain\State\Smoothing\LotHistoryData.cs:line 98
       at Locnes.Core.Domain.State.Smoothing.VdewmaStateEngine.<>c__DisplayClass35_0.<AdvanceState>b__0(LotHistoryData d) in C:\src\locnes\Locnes.Core\Domain\State\Smoothing\VdewmaStateEngine.cs:line 78
       at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
       at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
       at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
       at Locnes.Core.Domain.State.Smoothing.VdewmaStateEngine.AdvanceState(EngineData history, IDataStrategyService service) in C:\src\locnes\Locnes.Core\Domain\State\Smoothing\VdewmaStateEngine.cs:line 78
       at Locnes.Core.Domain.State.Smoothing.TermEngine.DoGetAverage() in C:\src\locnes\Locnes.Core\Domain\State\Smoothing\TermEngine.cs:line 132
       at Locnes.Core.Domain.State.Smoothing.TermEngine.<>c__DisplayClass35_0.<GetAverage>b__0() in C:\src\locnes\Locnes.Core\Domain\State\Smoothing\TermEngine.cs:line 118
       at CSharpFunctionalExtensions.Result.Try[T](Func`1 func, Func`2 errorHandler)

SessionFactory config

                var dbConfig = OracleDataClientConfiguration.Oracle10
                    .ConnectionString(c => c.Is(connectionString))
                    .Driver<NHibernate.Driver.OracleManagedDataClientDriver>();
                //.ShowSql();
                dbConfig = ApplicationGlobals.DefaultSchema.Equals(ApplicationGlobals.WindowsUser) ? dbConfig : dbConfig.DefaultSchema(ApplicationGlobals.DefaultSchema);

                FluentConfiguration configuration = Fluently.Configure()
                    .Database(dbConfig)
                    .Mappings(m =>
                    {
                        m.FluentMappings.AddFromAssembly(targetAssembly).AddConventions() /*.ExportTo(@"c:\temp\mappings")*/;
                        //m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()).AddConventions()/*.ExportTo(@"c:\temp\mappings")*/;
                        //m.HbmMappings.AddFromAssembly(Assembly.GetExecutingAssembly());
                    })
                    .ExposeConfiguration(x =>
                    {
                        x.EventListeners.PostCommitUpdateEventListeners = new IPostUpdateEventListener[] { new EventListener() };
                        x.EventListeners.PostCommitInsertEventListeners = new IPostInsertEventListener[] { new EventListener() };
                        x.EventListeners.PostCommitDeleteEventListeners = new IPostDeleteEventListener[] { new EventListener() };
                        x.EventListeners.PostCollectionUpdateEventListeners = new IPostCollectionUpdateEventListener[] { new EventListener() };
                        x.DataBaseIntegration(dbi =>
                        {
                            dbi.Batcher<OracleDataClientBatchingBatcherFactory>();
                            dbi.Dialect<Oracle10gDialect>();
                            dbi.BatchSize = 100;
                        });
                        x.SetProperty(Environment.DefaultBatchFetchSize, "50");
                        x.Cache(cache => cache.Provider<HashtableCacheProvider>());
                        x.SetInterceptor(new ReferenceDataInterceptor());
                        x.SetInterceptor(new SqlStatementInterceptor());
                    });

I have a separate query routine that uses the Oracle.ManagedDataAccess dll that was also bombing out for an InvalidCastException when used against similar data. I verified using the query that the issue resolved when I changed the value of the problematic number at the last decimal place). I'm still trying to figure out the exact source of the problem under NHibernate, but the signature is basically the same InvalidCastException at GetDecimal().

Example value that causes cast exception: 0.00000000000000422030887989616

Let me know if I can provide any additional information. Appreciate any help you can provide; I'm looking at a pretty ugly issue if I don't get this resolved soon.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions