Mybatis.NET Oracle 线上神奇问题:Value does not fall within the expected range.

1、错误现象

 

  在向数据库查询一条数据的时候报如下错误:

1    Value does not fall within the expected range.
2    at Oracle.ManagedDataAccess.Client.OracleParameter.set_Value(Object value)
3    at MyBatis.DataMapper.Data.DefaultPreparedCommand.ApplyParameterMap(IDbProvider dbProvider, IDbCommand command, RequestScope request, IStatement statement, Object parameterObject)
4    at MyBatis.DataMapper.Data.DefaultPreparedCommand.Create(RequestScope request, ISession session, IStatement statement, Object parameterObject)
5    at MyBatis.DataMapper.MappedStatements.MappedStatement.Execute[T](Object preEvent, Object postEvent, ISession session, Object parameterObject, Func`3 requestRunner)
6    at MyBatis.DataMapper.MappedStatements.MappedStatement.ExecuteInsert(ISession session, Object parameterObject)
7    at MyBatis.DataMapper.DataMapper.Insert(String statementId, Object parameterObject)

 错误信息也很简单。在向OracleParameter的属性Value赋值时报错。

 

2、找不出的错误原因

  源码如下:

  

 1 [Category("Data"), Description(""), DefaultValue((string) null)]
 2 public override object Value
 3 {
 4     get => 
 5         this.m_value;
 6     set
 7     {
 8         if (((value != null) && (value != DBNull.Value)) && (this.m_enumType == PrmEnumType.NOTSET))
 9         {
10             Type type = value.GetType();
11             if (((type == typeof(sbyte)) || (type == typeof(ushort))) || ((type == typeof(uint)) || (type == typeof(ulong))))
12             {
13                 throw new ArgumentException();
14             }
15             object obj2 = OraDb_DbTypeTable.s_table[type];
16             if ((obj2 == null) && type.IsArray)
17             {
18                 obj2 = OraDb_DbTypeTable.s_table[type.GetElementType()];
19             }
20             if (obj2 == null)
21             {
22                 throw new ArgumentException();
23             }
24             this.m_oraDbType = (OracleDbType) obj2;
25             this.m_bSetDbType = false;
26             this.m_enumType = PrmEnumType.VALUE;
27         }
28         this.m_value = value;
29     }
30 }
31  

  从源码可以看到,报错可能出现在两个地方:if (((type == typeof(sbyte)) || (type == typeof(ushort))) || ((type == typeof(uint)) || (type == typeof(ulong))))【更老的驱动版本bool类型也不支持】 和 if (obj2 == null)。出现这个错误说明无法将C#类型映射为Oracle类型。

  首先检查第一处可能,发现插入报错的类中使用的类型有DateTime,DateTime?,long,int,string。发现第一处不满足。

  检查第二次可能,第二处取类型是从 OraDb_DbTypeTable 获取到的。源码如下:

  

 1 internal static void InsertTableEntries()
 2 {
 3     s_table.Add(typeof(byte), OracleDbType.Byte);
 4     s_table.Add(typeof(byte[]), OracleDbType.Raw);
 5     s_table.Add(typeof(char), OracleDbType.Varchar2);
 6     s_table.Add(typeof(char[]), OracleDbType.Varchar2);
 7     s_table.Add(typeof(DateTime), OracleDbType.TimeStamp);
 8     s_table.Add(typeof(short), OracleDbType.Int16);
 9     s_table.Add(typeof(int), OracleDbType.Int32);
10     s_table.Add(typeof(long), OracleDbType.Int64);
11     s_table.Add(typeof(float), OracleDbType.Single);
12     s_table.Add(typeof(double), OracleDbType.Double);
13     s_table.Add(typeof(decimal), OracleDbType.Decimal);
14     s_table.Add(typeof(string), OracleDbType.Varchar2);
15     s_table.Add(typeof(TimeSpan), OracleDbType.IntervalDS);
16     s_table.Add(typeof(OracleBFile), OracleDbType.BFile);
17     s_table.Add(typeof(OracleBinary), OracleDbType.Raw);
18     s_table.Add(typeof(OracleBlob), OracleDbType.Blob);
19     s_table.Add(typeof(OracleClob), OracleDbType.Clob);
20     s_table.Add(typeof(OracleDate), OracleDbType.Date);
21     s_table.Add(typeof(OracleDecimal), OracleDbType.Decimal);
22     s_table.Add(typeof(OracleIntervalDS), OracleDbType.IntervalDS);
23     s_table.Add(typeof(OracleIntervalYM), OracleDbType.IntervalYM);
24     s_table.Add(typeof(OracleRefCursor), OracleDbType.RefCursor);
25     s_table.Add(typeof(OracleString), OracleDbType.Varchar2);
26     s_table.Add(typeof(OracleTimeStamp), OracleDbType.TimeStamp);
27     s_table.Add(typeof(OracleTimeStampLTZ), OracleDbType.TimeStampLTZ);
28     s_table.Add(typeof(OracleTimeStampTZ), OracleDbType.TimeStampTZ);
29     s_table.Add(typeof(OracleXmlType), OracleDbType.XmlType);
30     s_table.Add(typeof(bool), OracleDbType.Boolean);
31     s_table.Add(typeof(DateTimeOffset), OracleDbType.TimeStampTZ);
32 }

  发现里面没有DateTime?,难道是这个地方不行。那也不对啊,其它地方同样类型是可以保存到数据库的啊。继续找下去在 MyBatis.DataMapper.Data.DefaultPreparedCommand.ApplyParameterMap 找到了信息。源码如下:

  

 1 public virtual void SetParameter(IDataParameter dataParameter, object parameterValue, string dbType)
 2 {
 3     if (parameterValue != null)
 4     {
 5         dataParameter.Value = parameterValue;
 6     }
 7     else
 8     {
 9         dataParameter.Value = DBNull.Value;
10     }
11 }

  如果值为null,会自动赋值为 DBNull.Value 也就会不会报错。既然都不会出现问题,可问题又会出现在哪里呢?

 

3、柳暗花明

  在本地尝试各种方法后仍然不能重现,只能让领导将生产程序包拷贝一份下来进行尝试。

  包拿到之后,进行必要配置启动,出现了新的诡异问题 Redis报错 :No connection is available to service this operation。仔细检查Redis配置发现问题。抓下内存快照看看实际配置信息是什么:

  Mybatis.NET Oracle 线上神奇问题:Value does not fall within the expected range._第1张图片

 

   看到这个一脸懵逼,Password竟然是null,明明已经配置了啊。查看了加载配置的源码,发现根本不会解析Password配置,还有这种操作,这程序包是有多老。弄好了Redis,启动保存数据,果然不行。反编译生产源码,发现惊天一幕:

  

 

   Id的类型竟然ulong。我擦这是多久没有更新了。原因至此是找到了。

 

4、吐槽

 

  这个服务是公司内部自己用的,很古老了。以前都是手工拷贝程序包发布的。最近由于公司发生了些意外事情,原来服务器不能用,需要部署新的服务器上。由于古老,不知道老大当时拿了多么古老的程序包部署了。

 

你可能感兴趣的