這邊用個例子帶讀者了解DynamicParameter原理,舉例現在有一段代碼如下 :
using (var cn = Connection)
{
var paramter = new { Name = "John", Age = 25 };
var result = cn.Query("select @Name Name,@Age Age", paramter).First();
}
前面已經知道String型態Dapper會自動將轉成資料庫Nvarchar
並且長度為4000
的參數,資料庫實際執行的SQL如下 :
exec sp_executesql N'select @Name Name,@Age Age',N'@Name nvarchar(4000),@Age int',@Name=N'John',@Age=25
這是一個方便快速開發的貼心設計,但假如遇到欄位是varchar
型態的情況,有可能會因為隱性轉型導致索引失效
,導致查詢效率變低。
這時解決方式可以使用Dapper DynamicParamter指定資料庫型態跟大小,達到優化效能目的
using (var cn = Connection)
{
var paramters = new DynamicParameters();
paramters.Add("Name","John",DbType.AnsiString,size:4);
paramters.Add("Age",25,DbType.Int32);
var result = cn.Query("select @Name Name,@Age Age", paramters).First();
}
接著往底層來看如何實現,首先關注GetCacheInfo方法,可以看到DynamicParameters建立動態方法方式代碼很簡單,就只是呼叫AddParameters方法
Action<IDbCommand, object> reader;
if (exampleParameters is IDynamicParameters)
{
reader = (cmd, obj) => ((IDynamicParameters)obj).AddParameters(cmd, identity);
}
代碼可以這麼簡單的原因,是Dapper在這邊特別使用「依賴於介面」
設計,增加程式的彈性
,讓使用者可以客制自己想要的實作邏輯。這點下面會講解,首先來看Dapper預設的實作類別DynamicParameters
中AddParameters
方法的實作邏輯
public class DynamicParameters : SqlMapper.IDynamicParameters, SqlMapper.IParameterLookup, SqlMapper.IParameterCallbacks
{
protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
var literals = SqlMapper.GetLiteralTokens(identity.sql);
foreach (var param in parameters.Values)
{
if (param.CameFromTemplate) continue;
var dbType = param.DbType;
var val = param.Value;
string name = Clean(param.Name);
var isCustomQueryParameter = val is SqlMapper.ICustomQueryParameter;
SqlMapper.ITypeHandler handler = null;
if (dbType == null && val != null && !isCustomQueryParameter)
{
#pragma warning disable 618
dbType = SqlMapper.LookupDbType(val.GetType(), name, true, out handler);
#pragma warning disable 618
}
if (isCustomQueryParameter)
{
((SqlMapper.ICustomQueryParameter)val).AddParameter(command, name);
}
else if (dbType == EnumerableMultiParameter)
{
#pragma warning disable 612, 618
SqlMapper.PackListParameters(command, name, val);
#pragma warning restore 612, 618
}
else
{
bool add = !command.Parameters.Contains(name);
IDbDataParameter p;
if (add)
{
p = command.CreateParameter();
p.ParameterName = name;
}
else
{
p = (IDbDataParameter)command.Parameters[name];
}
p.Direction = param.ParameterDirection;
if (handler == null)
{
#pragma warning disable 0618
p.Value = SqlMapper.SanitizeParameterValue(val);
#pragma warning restore 0618
if (dbType != null && p.DbType != dbType)
{
p.DbType = dbType.Value;
}
var s = val as string;
if (s?.Length <= DbString.DefaultLength)
{
p.Size = DbString.DefaultLength;
}
if (param.Size != null) p.Size = param.Size.Value;
if (param.Precision != null) p.Precision = param.Precision.Value;
if (param.Scale != null) p.Scale = param.Scale.Value;
}
else
{
if (dbType != null) p.DbType = dbType.Value;
if (param.Size != null) p.Size = param.Size.Value;
if (param.Precision != null) p.Precision = param.Precision.Value;
if (param.Scale != null) p.Scale = param.Scale.Value;
handler.SetValue(p, val ?? DBNull.Value);
}
if (add)
{
command.Parameters.Add(p);
}
param.AttachedParam = p;
}
}
// note: most non-priveleged implementations would use: this.ReplaceLiterals(command);
if (literals.Count != 0) SqlMapper.ReplaceLiterals(this, command, literals);
}
}
可以發現Dapper在AddParameters為了方便性跟兼容其他功能,像是Literal Replacement、EnumerableMultiParameter功能,做了許多判斷跟動作,所以代碼量會比以前使用ADO.NET版本多,所以效率也會比較慢。
假如有效率苛求的需求,可以自己實作想要的邏輯,因為Dapper此段特別設計成「依賴於介面」
,只需要實作IDynamicParameters
介面就可以。
以下是我做的一個Demo,可以使用ADO.NET SqlParameter建立參數跟Dapper配合
public class CustomPraameters : SqlMapper.IDynamicParameters
{
private SqlParameter[] parameters;
public void Add(params SqlParameter[] mParameters)
{
parameters = mParameters;
}
void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
if (parameters != null && parameters.Length > 0)
foreach (var p in parameters)
command.Parameters.Add(p);
}
}