# 使用 Dapper 和 ODAC Managed Driver 無法寫入 Unicode 的問題
[](https://hackmd.io/AADHflk-QW-njCHfFmpsGg)
## 前言
在使用 ADO.NET 的過程中,通常情況下我不會特別去設定參數的 `DbType`,除非是在自己撰寫的架構中有掃描資料庫結構並自動生成程式碼。然而,最近我在使用 Oracle 資料庫時遇到了亂碼的問題。
當客戶向我反應資料寫入簡體中文時出現亂碼時,一開始我還疑惑是哪邊有問題。後來客戶認為是因為沒有設定 `OracleDbType`,而在 Dapper 中為了支援多種資料庫,使用的是 `DbType`。於是我嘗試設定對應的 `DbType.String`,希望能解決問題,結果仍然是一樣的亂碼結果。我檢查了之前成功寫入簡體中文的資料,證實資料庫的編碼設定應該是正確的。我還懷疑是連線字串或其他設定的問題,於是上網查詢了一下相關資訊。
我發現 Oracle 資料庫的亂碼問題似乎滿常見的,但找到的資訊都不太符合我的情況。在查了一堆資料和被 ChatGPT 欺騙大量感情後。終於在***黑暗執行序***大神看到相關的文章「[Hacking樂無窮:修正Dapper+ODP.NET無法寫入Unicode問題](https://blog.darkthread.net/blog/dapper-odpnet-unicode-issue/)」。這個問題是 ODP.NET 的一個 Bug,導致 `DbType.String` 沒有成功對應到 `OracleDbType.NVarchar2`。但這已經是六年前的文章,而我目前使用的套件是「Oracle.ManagedDataAccess.Core」,怎還有一樣的問題?
## 對應關係檢查
透過使用 Visual Studio 內建的反組譯功能,我們可以觀察到以下三個 enum 的對應值:
* `DbType.String`:16
* `OracleDbType.NVarchar2`:119
* `OracleDbType.Varchar2`:126
 
然而,根據目前最新版本(3.21.100)的「Oracle.ManagedDataAccess.Core」程式碼來看,依然是將 `DbType.String`對應到 `OracleDbType.Varchar2`...。

## 自訂 `DynamicParameters`
由於 Dapper 不直接支援使用 `IDbDataParameter` 作為參數,所以無法直接建立具有正確 `OracleDbType` 的 `OracleParameter`。所以,我只好自訂一個 `DynamicParameters` 的類別來處理這個問題。
```csharp
public class MyDynamicParameters : SqlMapper.IDynamicParameters
{
private readonly Dapper.DynamicParameters dynamicParameters = new();
private readonly List<IDbDataParameter> dbDataParameters = new();
// 使用轉發方式將 Dapper.DynamicParameters 相關的 API 實作一份,以下示範了一個方法
public void Add(string name, object value, DbType? dbType, ParameterDirection? direction, int? size)
{
dynamicParameters.Add(name, value, dbType, direction, size);
}
// ...其他 API 的轉發...
// 增加對 IDbDataParameter 參數的支援
public void Add(IDbDataParameter paramerter)
{
dbDataParameters.Add(paramerter);
}
// 使Dapper.DynamicParameters 是用明確實作,這段程式通常也不應該被外部直接呼叫
void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
AddParameters(command, identity);
}
// 增加參數並轉發至 Dapper.DynamicParameters,並將 IDbDataParameter 參數加入到 IDbCommand 的參數集合中
// 本來想 override Dapper.DynamicParameters.AddParameters 就不用寫轉發,結果不支援 override...
protected void AddParameters(IDbCommand command, SqlMapper.Identity Identity)
{
// 因為原本是明確實作,所以要轉型成 SqlMapper.IDynamicParameters 才可呼叫 AddParameters
((SqlMapper.IDynamicParameters)dynamicParameters).AddParameters(command, Identity);
foreach (IDbDataParameter p in dbDataParameters)
{
command.Parameters.Add(p);
}
}
}
```
使用方法如下,將原本傳入的 `DynamicParameters` 改為使用 `MyDynamicParameters`。
```csharp
using (IDbConnection conn = new OracleConnection(connStr)) {
conn.Open();
MyDynamicParameters parameters = new();
parameters.Add(new OracleParameter
{
ParameterName = "Name",
Value = value,
OracleDbType = OracleDbType.NVarchar2
});
conn.Query(sql, parameters);
}
```
###### tags: `.NET` `Oracle`