在同事的專案採集到一枚奇特茶包。程式看似無誤,欄位也宣告成NVARCHAR,但塞入的Unicode難字硬是變亂碼,以下程式片段可重現問題:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.OracleClient;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
staticstring cnStr =
"Data Source=MyORACLE;User Id=user;Password=pwd;";
staticvoid Main(string[] args)
{
using (OracleConnection cn = new OracleConnection(cnStr))
{
cn.Open();
var cmd = cn.CreateCommand();
cmd.CommandText = "INSERT INTO JEFFUNICODE VALUES (:t)";
var p = cmd.Parameters.Add("t", SqlDbType.NVarChar);
string s = "犇";
p.Value = s;
cmd.ExecuteNonQuery();
cmd.CommandText = "SELECT * FROM JEFFUNICODE";
cmd.Parameters.Clear();
var dr = cmd.ExecuteReader();
dr.Read();
var t = dr["T"].ToString();
Func<string, string> charCode = str =>
BitConverter.ToString(Encoding.UTF8.GetBytes(str));
Debug.WriteLine(string.Format("{0}[{1}]->{2}[{3}]",
s, charCode(s), t, charCode(t)));
}
}
}
}
執行結果為: 犇[E7-8A-87]->[EE-9B-95]
把眼睛睜大20%重新看一次程式,才發現裡面有個地方錯得離譜,明明是OracleCommand.Parameters.Add(),怎麼用SqlDbType.NVarChar? 要命的是還可以編譯可以執行?
修正為 var p = cmd.Parameters.Add("t", OracleType.NVarChar); ,一切就正常了。
犇[E7-8A-87]->犇[E7-8A-87]
整起事件離奇之處在於 -- 依System.Data.OracleClient.OracleParameterCollection的定義:
public OracleParameter Add(string parameterName, OracleType dataType);
為什麼傳入SqlDbType不會產生編譯錯誤? 強型別的C#接受了錯誤的型別,還可以編譯、執行,這一點都不科學啊! 難道,拎北這十幾年的.NET白學了? 眼睛再張大到50%(啊啊啊,眼角都撐到滲血了)重看一次定義才發現蹊蹺, Add()在兩個參數時還有一個Overloading:
public OracleParameter Add(string parameterName, object value);
原來,當第二個參數不是OracleType,SqlDbType.NVarChar被視為object,套用的是(string, object) Overloading,於是SqlDbType被誤當成OracleParameter.Value。以下程式可以證實:
var p = cmd.Parameters.Add("t", SqlDbType.NVarChar);
Console.WriteLine(p.DbType + "/" + p.Value.GetType());
結果為: Int32/System.Data.SqlDbType ,真相大白!
準備收工時才發現Visual Studio早給了提示: Parameters.Add()下方有條綠蚯蚓,提到.Add(string, object)已過時(deprecated)不建議使用,請改用AddWithValue()... 結果我還查到眼角流血才破案 XD
題外話,設計Overloading時要小心處理object型別,因為所有參數型別都可視為object,較容易套用錯誤發生非預期結果。過去開發時,看到有些Overloading在參數數量及順序做了特殊安排,起初覺得不自然,後來才意會到跟減少混淆誤用有關,是一種防呆設計,API的好壞就都在這些細節中囉~