偷看 Dapper 產生的 SQL 指令與參數型別
source link: https://blog.darkthread.net/blog/spy-parameter-types-in-dapper/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
偷看 Dapper 產生的 SQL 指令與參數型別
2024-01-16 08:53 PM | 0 | 1,182 |
這是調查 Dapper 參數茶包的副產品,檢查底層對映參數型別的工具函式。
遇到一個 Dapper 問題,懷疑與匿名參數物件屬性對映的 OracleParameter 型別有關。而 Oracle 不像 MSSQL 有 Profiler 可以偷看實體指令,我便想從程式端突破,設法在執行時取得參數資訊。原本我寄望於現成程式庫,但找到最接近的解決方案是 MiniProfiler.Integrations ,預設只支援 SQL Server / MySQL,如果要用在 Oracle,得寫幾行程式為 OracleConnection 實作 IDbConnectionFactory
:
public class OracleDbConnectionFactory : IDbConnectionFactory
{
private readonly string _connectionString;
public OracleDbConnectionFactory(string connectionString)
{
_connectionString = connectionString;
}
public DbConnection CreateConnection()
{
return new OracleConnection(_connectionString);
}
}
是有成功印出 SQL 指令跟參數,它的參數資訊為 Dictionary<string, object>
型別,只看得到參數名稱跟參數值,無法得知 OracleParameter.OracleDbType:
懶得再繼續試其他程式庫,不如動手自己寫吧。我追進 Dapper 原始碼,發現 IDbCommand 的參數會透過 CacheInfo.ParamReader(IDbCommand, object?) 產生,只是 CacheInfo 的相關型別、方法、建構式都屬內部 API,不開放外部呼叫。所幸,有 Reflection 在手,即使標成 internal、private 也攔不住我們,層層解開,終於一窺底層樣貌:
SimulateDapperDbParameters() 會模擬 Dapper 執行 IDbCommand 的過程,將參數一一對映成 OracleParameter 或 SqlParameter (若需支援其他資料庫可自行增加),實測 Oracle 及 SQL 可抓到實際的參數物件型別,還順便驗證 FixOdpNetDbTypeStringMapping 確實可修正將 string 對映到 NVarChar2,證明有抓取到正確結果。
範例程式碼如下,有需要的同學請自取修改利用:(注意:GetConstructors() 取建構式部分我偷懶寫死 [1] 取陣列第二個 Overloading,而 Dapper 新舊版的參數數量及型別可能不同,若不相容請自行調整)
using Dapper;
using Oracle.ManagedDataAccess.Client;
using StackExchange.Profiling.Internal;
using System;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Dynamic;
using System.Linq;
using System.Reflection;
using System.Transactions;
namespace NetFxConsole
{
internal class Program
{
// TODO: encrypt the connection string and store it in the app.config
private const string csOracle =
"Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=SqlDB)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=XEPDB1)));User Id=LAB;Password=****;";
private const string csMsSql = "Data Source=SqlDB;Initial Catalog=LAB;User Id=LAB;Password=****;";
static object GetSqlMapperParamReader(CommandDefinition cmd, IDbConnection cnn, object paramArg)
{
var sqlMapperType = typeof(SqlMapper);
var sqlMapperIdentityType = sqlMapperType.Assembly.GetType("Dapper.SqlMapper+Identity");
// TODO: hard-coded here, array index and parameters may differ in different versions of Dapper
var identity = sqlMapperIdentityType.GetConstructors(
BindingFlags.Instance | BindingFlags.NonPublic)[1].Invoke(new object[]
{
cmd.CommandText, cmd.CommandType, string.Empty, typeof(string), paramArg.GetType(), null, 0
});
dynamic cacheInfo = sqlMapperType.GetMethod("GetCacheInfo", BindingFlags.Static | BindingFlags.NonPublic)
.Invoke(null, new object[] { identity, paramArg, false });
return cacheInfo.GetType().GetProperty("ParamReader").GetValue(cacheInfo, null);
}
public static void SimulateDapperDbParameters(string commandText, IDbConnection cnn, object paramArg)
{
var command = new CommandDefinition(commandText, paramArg);
var paramReader = GetSqlMapperParamReader(command, cnn, paramArg);
var mi = command.GetType().GetMethod("SetupCommand", BindingFlags.Instance | BindingFlags.NonPublic);
var cmd = mi.Invoke(command, new object[] { cnn, paramReader });
switch (cmd)
{
case OracleCommand oc:
Console.WriteLine($"CommandText: \x1b[33m{oc.CommandText}\x1b[0m");
foreach (OracleParameter p in oc.Parameters)
{
Console.WriteLine($" * \u001b[36m{p.ParameterName,-8}\u001b[0m / {p.DbType,-8} / \u001b[35m{$"{p.OracleDbType}({p.Size})",-16} \u001b[0m = {p.Value}");
}
break;
case SqlCommand sc:
Console.WriteLine($"CommandText: \x1b[33m{sc.CommandText}\x1b[0m");
foreach (SqlParameter p in sc.Parameters)
{
Console.WriteLine($" * \u001b[36m{p.ParameterName,-8}\u001b[0m / {p.DbType,-8} / \u001b[35m{$"{p.SqlDbType}({p.Size})",-16}\u001b[0m = {p.Value}");
}
break;
default:
throw new NotSupportedException();
}
}
static void Main(string[] args)
{
var sql = "INSERT INTO SAMPLE VALUES (:name, :regDate, :score)";
var paramObject = new
{
name = "Jeffrey",
regDate = new DateTime(2012, 12, 21),
score = 32767
};
using (var cn = new OracleConnection(csOracle))
{
SimulateDapperDbParameters(sql, cn, paramObject);
FixOdpNetDbTypeStringMapping();
SimulateDapperDbParameters(sql, cn, paramObject);
}
using (var cn = new SqlConnection(csMsSql))
{
sql = sql.Replace(":", "@");
SimulateDapperDbParameters(sql, cn, paramObject);
}
Console.ReadLine();
}
// https://blog.darkthread.net/blog/dapper-odpnet-unicode-issue/
static void FixOdpNetDbTypeStringMapping()
{
Assembly asm = typeof(OracleConnection).Assembly;
Type tOraDb_DbTypeTable = asm.GetType("Oracle.ManagedDataAccess.Client.OraDb_DbTypeTable");
var fldDbTypeMapping = tOraDb_DbTypeTable.GetField("dbTypeToOracleDbTypeMapping",
BindingFlags.Static | BindingFlags.NonPublic);
int[] mappings = (int[])fldDbTypeMapping.GetValue(null);
mappings[(int)System.Data.DbType.String] = (int)OracleDbType.NVarchar2;
fldDbTypeMapping.SetValue(null, mappings);
}
}
}
補充顯示資料時用到兩則小技巧:
Recommend
-
115
80% 的人画错苹果 Logo:不信你也试试,不许偷看手机!_详细解读_最新资讯_热点事件_36氪80% 的人画错苹果 Logo:不信你也试试,不许偷看手机!神译局
-
25
深夜,男子偷看妻子微信聊天记录,怀疑妻子出轨且丈母娘知情。盛怒之下,男子先后到厨房取来一把尖刀和一把菜刀,残忍地将妻子和岳母杀害,而年仅6岁的儿子也在家中。8月30日上午,辽宁大连市中级人民法院对这起案件进行了开庭审理。法庭上男子希望判自己死刑,立...
-
58
臭猫!为了偷看还学会了推开窗户!
-
43
偷看心上猫被发现
-
30
沙雕版lemon,又是谁在偷看我的生活
-
20
尽管前几天刚刚发布的 iOS 14 目前还处于第一个 Beta 版,但无数的苹果用户已经按捺不住自己激流勇进的心情,纷纷下载描述文件安装了起来直升 iOS 14。 就像 MIUI 12 的更新揪出了不少不守规矩的牛鬼蛇神,这次 iOS 14 在隐私方...
-
3
捂脸偷偷看~唐朝与宋朝,到底哪个更开放? 古代服饰 时间:2021-12-20 09:27 | 阅读: 401
-
4
指缝偷看系列丨《塞尔达传说:姆吉拉的假面》附身怪谈真相“Ben Drowned” Your web browser (Chrome 77) is out of date. Update your browser for more security, speed and the best experience on this site.
-
5
工资单被同事偷看,盒马以「泄露机密」开除该员工,这合理吗?工资为什么要保密?创业等 2 个话题下的优秀答主2月16日,山...
-
7
觀察 EF Core 產生的 SQL 指令 2020-07-26 09:20 AM 5 4,375...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK