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配置发现问题。抓下内存快照看看实际配置信息是什么:
看到这个一脸懵逼,Password竟然是null,明明已经配置了啊。查看了加载配置的源码,发现根本不会解析Password配置,还有这种操作,这程序包是有多老。弄好了Redis,启动保存数据,果然不行。反编译生产源码,发现惊天一幕:
Id的类型竟然ulong。我擦这是多久没有更新了。原因至此是找到了。
4、吐槽
这个服务是公司内部自己用的,很古老了。以前都是手工拷贝程序包发布的。最近由于公司发生了些意外事情,原来服务器不能用,需要部署新的服务器上。由于古老,不知道老大当时拿了多么古老的程序包部署了。