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、吐槽
這個服務是公司內部自己用的,很古老了。以前都是手工拷貝程序包發布的。最近由於公司發生了些意外事情,原來服務器不能用,需要部署新的服務器上。由於古老,不知道老大當時拿了多么古老的程序包部署了。