Dapper完美兼容Oracle,執行存儲過程,並返回結果集。
這個問題,困擾了我整整兩天。
剛剛用到Dapper的時候,感覺非常牛掰。特別是配合.net 4.0新特性dynamic,讓我生成泛型集合,再轉json一氣呵成。
不過,各種ORM總有讓人吐槽的地方。。。
比如,我之前在SqlServer上寫測試,搞封裝,沒有任何問題。CURD、批量操作、存儲過程、事物等。
可是以轉到Oracle上,就出問題了【喂~不是說好的支持Oracle的么】
在寫Dapper+Oracle單元測試的前期,是沒有問題的,也就是說普通的Sql操作是沒有任何問題的。
然后,我寫到存儲過程的單元測試的時候,就蛋疼了。
因為原版采用的DbType數據類型枚舉。Sqlserver返回結果集並沒有輸出游標。
但是Oracle輸出結果集,就需要用游標了。那么,這里問題就來了。給OracleParameter設置參數類型,DbType並沒有Cursor游標類型。
關於Dapper的文檔也是不多,而且大部分都集中在SqlServer上,可能應為服務於.Net平台,比較側重於微軟的配套數據庫。
好吧,問題來了,那就解決。反正是開源的。源代碼都有。
先根據問題來搜索【我不喜歡用百度,因為百度搜出來一大堆不相關的東西,銅臭味太重。google在國內有無法訪問,我就選擇了Bing,結果效果還不錯。】
經過網上搜集,發現Dapper確實是支持Oracle的,但是對於調用Oracle存儲過程的內容卻沒有。
好吧,沒有的話,先自己分析分析。
既然是參數類型不支持,那么換成支持的不就成了?
原版的是這樣的:
1 DynamicParameters dp = new DynamicParameters(); 2 dp.Add("RoleId", "1"); 3 dp.Add("RoleName", "", DbType.String, ParameterDirection.Output);
這是Dapper原版中,聲明parameter的部分,上面代碼紅色部分,就是指定參數類型。
在system.data.oracleclient 中,有OracleType這個枚舉有Cursor類型。
然后,去查看 DynamicParameters 類,如下圖:

可以看到,這個類,是實現了一個接口的。說明,原作者給我們預留了接口去自己實現其他內容。
繼續看看接口:

接口的內容很簡單,就是一個AddParameters方法。
那么,可以確定,上面的猜測是對的。
我們直接擴展實現這個接口就可以了。如圖:

自己去創建一個實現了IDynamicParameters的類OracleDynamicParameters。
然后參照原作者提供的DynamicParameters類來實現這個接口。
最終修改版如下(代碼多,展開了直接復制代碼貼到你的文件里面):
public class OracleDynamicParameters : SqlMapper.IDynamicParameters { private readonly DynamicParameters _dynamicParameters = new DynamicParameters(); private readonly List<OracleParameter> _oracleParameters = new List<OracleParameter>(); public void Add(string name, object value = null, DbType dbType = DbType.AnsiString, ParameterDirection? direction = null, int? size = null) { _dynamicParameters.Add(name, value, dbType, direction, size); } public void Add(string name, OracleType oracleDbType, ParameterDirection direction) { var oracleParameter = new OracleParameter(name, oracleDbType) { Direction = direction }; _oracleParameters.Add(oracleParameter); } public void Add(string name, OracleType oracleDbType, int size, ParameterDirection direction) { var oracleParameter = new OracleParameter(name, oracleDbType, size) { Direction = direction }; _oracleParameters.Add(oracleParameter); } public void AddParameters(IDbCommand command, SqlMapper.Identity identity) { ((SqlMapper.IDynamicParameters)_dynamicParameters).AddParameters(command, identity); var oracleCommand = command as OracleCommand; if (oracleCommand != null) { oracleCommand.Parameters.AddRange(_oracleParameters.ToArray()); } } public T Get<T>(string parameterName) { var parameter = _oracleParameters.SingleOrDefault(t => t.ParameterName == parameterName); if (parameter != null) return (T)Convert.ChangeType(parameter.Value, typeof(T)); return default(T); } public T Get<T>(int index) { var parameter = _oracleParameters[index]; if (parameter != null) return (T)Convert.ChangeType(parameter.Value, typeof(T)); return default(T); } } public sealed class DbString { public DbString() { Length = -1; } public bool IsAnsi { get; set; } public bool IsFixedLength { get; set; } public int Length { get; set; } public string Value { get; set; } public void AddParameter(IDbCommand command, string name) { if (IsFixedLength && Length == -1) { throw new InvalidOperationException("If specifying IsFixedLength, a Length must also be specified"); } var param = command.CreateParameter(); param.ParameterName = name; param.Value = (object)Value ?? DBNull.Value; if (Length == -1 && Value != null && Value.Length <= 4000) { param.Size = 4000; } else { param.Size = Length; } param.DbType = IsAnsi ? (IsFixedLength ? DbType.AnsiStringFixedLength : DbType.AnsiString) : (IsFixedLength ? DbType.StringFixedLength : DbType.String); command.Parameters.Add(param); } }
ok,擴展寫完了,來一個單元測試,試一試:
1 /// <summary> 2 /// 執行帶參數存儲過程,並返回結果 3 /// </summary> 4 public static void ExectPro() 5 { 6 var p = new OracleDynamicParameters(); 7 p.Add("beginTime", 201501); 8 p.Add("endTime", 201512); 9 p.Add("targetColumn", "tax"); 10 p.Add("vCur", OracleDbType.RefCursor, ParameterDirection.Output); 11 using (IDbConnection conn = new OracleConnection(SqlConnOdp)) 12 { 13 conn.Open(); 14 var aa = conn.Query("p_123c", param: p, commandType: CommandType.StoredProcedure).ToList(); 15 aa.ForEach(m => Console.WriteLine(m.C_NAME)); 16 } 17 Console.ReadLine(); 18 }
結果執行通過,並打印了首列的所有值。
那么,Dapper的簡單擴展就完成了。
寫在后面
補充說明: 我用的Oracle驅動是ODP.NET,.net是4.0
這個ODP.NET的Oracle.DataAccess.dll推薦從你的目標服務器,復制回來,不要用本地的,反正我用本地的,就提示外部程序錯誤。猜測是版本問題或者是位數問題。
相關參考文章
http://stackoverflow.com/questions/6212992/using-dapper-with-oracle
https://stackoverflow.com/questions/15943389/using-dapper-with-oracle-user-defined-types
