Dapper優勢和缺點 優點 高性能、易排查、易運維、靈活可控 缺點 和EF相比,手寫sql當修改表結構不易發現bug。 習慣了EF後再來使用Dapper,會很難適應那種沒有了強類型的安全感。不過可以用單元測和心細來避免。 資料庫連接 問題:IDbConnection需不需要手動Open打開連接 答 ...
本文demo適用於MySQL
Dapper優勢和缺點
優點
高性能、易排查、易運維、靈活可控
缺點
和EF相比,手寫sql當修改表結構不易發現bug。
習慣了EF後再來使用Dapper,會很難適應那種沒有了強類型的安全感。不過可以用單元測和心細來避免。
資料庫連接
問題:IDbConnection需不需要手動Open打開連接
答案:有時候需要有時候不需要
Dapper連接可分兩種:主動管理(自己管理連接的打開和關閉)和自動管理(自動管理連接的打開和關閉)
//短短三行代碼即實現了dapper連接的主動管理和自動管理
bool wasClosed = cnn.State == ConnectionState.Closed;//判斷連接是否為關閉狀態
...
if (wasClosed) cnn.Open();
...
if (wasClosed) cnn.Close();
源碼位置 https://github.com/StackExchange/Dapper/blob/master/Dapper/SqlMapper.cs#L530
Note:ADO.NET預設是啟用連接池的 Pooling = true,連接池中最大連接數,預設為100
在使用Dapper的過程中,你有可能遇到過連接池超過最大限制。那問題是怎麼來的呢?
如果主動管理或者自動管理連接都不會有問題。就怕你管理一半,打開不關閉:
//迴圈執行兩百次左右就可以重現連接池超過最大限制
DBContext dBContext2 = new DBContext();
dBContext2.DbConnection.Open();
解決辦法相信不用我說了。
Note:在使用事務的時候需要手動打開連接,請不要忘記在finally裡面Close。
增刪改查的優化
批量新增
//1、可通過匿名對象集合進行參數化數據新增。(性能優化參考3)
DbConnection.Execute(sqlStr, ListEntity);
//2、【sql拼接可大大優化執行效率】在values後面帶上多有要插入的值。(如果數據太大可分批插入,如1000條一提交)
insert into tt (a,b,c,d) values (50,1,'1','1'), (51,2,'1','2');
//3、參數化防sql註入
var sql = insert into tt (a,b,c,d) values (@a1,@b1,@c1,@d1), (@a2,@b2,@c2,@d2);
DynamicParameters dynamicParameters = new DynamicParameters();
dynamicParameters.Add("a1","value");
dynamicParameters.Add("b1","value");
dynamicParameters.Add("c1","value");
dynamicParameters.Add("a2","value");
dynamicParameters.Add("b2","value");
dynamicParameters.Add("c2","value");
dynamicParameters.Add("d2","value");
DbConnection.ExecuteScalar<int>(sql, dynamicParameters)
批量修改
//1、可通過匿名對象集合進行參數化數據修改。(需要修改的值都不一樣的情況下,性能優化參考4)
DbConnection.Execute(sqlStr, ListEntity);
//2、如果需要修改的值都是一樣,只是條件不一樣。(使用SQL語句中的IN語法)
DbConnection.Execute("UPDATE tt SET aa = @aa where bb in @bb;", new { aa, bb });
//3、快速批量修改(此方法非常適合`新增或修改`數據的場景,可通過建聯合唯一索引來實現新增或修改的區分。【組合欄位不能為空,否則為空 不做唯一,有重覆空數據】)
insert into test_tbl (id,dr) values (1,'2'),(2,'3'),...(x,'y') on duplicate key update dr=values(dr);
//4、參數化防sql註入
var sql = insert into test_tbl (id,dr) values (@id1,@dr1),(@id2,@dr2),...(@idn,@drn) on duplicate key update dr=values(dr);
DynamicParameters dynamicParameters = new DynamicParameters();
dynamicParameters.Add("id1","value");
dynamicParameters.Add("dr1","value");
dynamicParameters.Add("id2","value");
dynamicParameters.Add("dr2","value");
...
dynamicParameters.Add("idn","value");
dynamicParameters.Add("drn","value");
DbConnection.ExecuteScalar<int>(sql, dynamicParameters)
批量刪除
同理,也可以使用參數化和IN語法
查詢第一條數據
dBContext.DbConnection.QueryFirstOrDefault<ItemFCLPO>("SELECT * from itemfcl_temp limit 1;"); //正確
dBContext.DbConnection.QueryFirstOrDefault<ItemFCLPO>("SELECT * from itemfcl_temp;"); //錯誤
dBContext.DbConnection.Query<ItemFCLPO>("SELECT * from itemfcl_temp;").FirstOrDefault(); //錯誤
dBContext.DbConnection.Query<ItemFCLPO>("SELECT * from itemfcl_temp;").ToList().FirstOrDefault();//錯誤
If擴展方法
使用過Mybatis的同學都知道,在xml裡面寫if、else還是蠻好用的。雖然我還是不喜歡在xml裡面寫sql。
那麼在Dapper裡面是不是也能簡便操作,答案是肯定的。這就得慶幸C#牛逼的語法了。
public static class StringExtension
{
public static string If(this string str, bool condition)
{
return condition ? str : string.Empty;
}
}
然後我們的sql就可以這樣拼接了
left join MaintenanceTemplates it on it.Id = m.MaintenanceTemplateId
where m.IsDeleted = 0
{" and m.Code = @KeyWord ".If(!string.IsNullOrWhiteSpace(input.KeyWord))}
{" and m.ProjectId = @ProjectId ".If(input.ProjectId.HasValue)}
{" and a.ProductId = @ProductId ".If(input.ProductId.HasValue)}
比起以前又臭又長的if判斷,個人感覺好多了。
Note:Dapper不會因為傳多了參數而報錯,所以放心使用If。
工作單元
使用EF的時候很方便做事務處理,而在Dapper中貌似就沒那麼優雅了。
我們每次在事務邏輯開始前都需要BeginTransaction
開啟,事務結束後都需要CommitTransaction
提交。代碼看起來也就稍顯混亂。
如果我們通過特性標記的方式,在標記了UnitOfWork
特性的方法自動開啟和提交事務那就完美了。如下:
[UnitOfWork]
public virtual void Test()
{
//執行業務邏輯
}
當然,這是可行的。通過AOP攔截,在方法執行前開啟事務,在方法執行後提交事務就可以了。
實現如下:
需要Nuget包Autofac.Extensions.DependencyInjection
Autofac.Extras.DynamicProxy
[UnitOfWork]
public virtual void DelUser()
{
var sql = "select * from UserTemp";
var userList = dBContext.DbConnection.Query<object>(sql);
var sql2 = $@"INSERT into UserTemp VALUES(0,'{DateTime.Now.ToString()}','sql2執行成功')";
dBContext.DbConnection.Execute(sql2);
throw new Exception("主動報錯");//驗證事務 是否有效
var sq3 = $@"INSERT into UserTemp VALUES(0,'{DateTime.Now.ToString()}','sq3執行成功')";
dBContext.DbConnection.Execute(sq3);
}
public class UnitOfWorkIInterceptor : IInterceptor
{
private DBContext dBContext;
public UnitOfWorkIInterceptor(DBContext dBContext)
{
this.dBContext = dBContext;
}
public void Intercept(IInvocation invocation)
{
MethodInfo methodInfo = invocation.MethodInvocationTarget;
if (methodInfo == null)
methodInfo = invocation.Method;
UnitOfWorkAttribute transaction = methodInfo.GetCustomAttributes<UnitOfWorkAttribute>(true).FirstOrDefault();
//如果標記了 [UnitOfWork],並且不在事務嵌套中。
if (transaction != null && dBContext.Committed)
{
//開啟事務
dBContext.BeginTransaction();
try
{
//事務包裹 查詢語句
//https://github.com/mysql-net/MySqlConnector/issues/405
invocation.Proceed();
//提交事務
dBContext.CommitTransaction();
}
catch (Exception ex)
{
//回滾
dBContext.RollBackTransaction();
throw;
}
}
else
{
//如果沒有標記[UnitOfWork],直接執行方法
invocation.Proceed();
}
}
}
完整的測試源碼,會在文末提供。
SQL監控
使用EF的同學應該很多人都知道MiniProfiler
,我在前些年分享EF的時候有做過簡單介紹。
那麼我們在執行Dapper的時候是不是也可以對生成的sql做檢測和性能監控。
答案是肯定的。Git地址
MiniProfiler監控套件還真不是一般的強。EF、MongoDB、MySql、Redis、SqlServer統統支持。
接下來我們實現對Dapper監控,導入Nuget包MiniProfiler.AspNetCore
public class ActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var profiler = MiniProfiler.StartNew("StartNew");
using (profiler.Step("Level1"))
{
//執行Action
await next();
}
WriteLog(profiler);
}
/// <summary>
/// sql跟蹤
/// 下載:MiniProfiler.AspNetCore
/// </summary>
/// <param name="profiler"></param>
private void WriteLog(MiniProfiler profiler)
{
if (profiler?.Root != null)
{
var root = profiler.Root;
if (root.HasChildren)
{
root.Children.ForEach(chil =>
{
if (chil.CustomTimings?.Count > 0)
{
foreach (var customTiming in chil.CustomTimings)
{
var all_sql = new List<string>();
var err_sql = new List<string>();
var all_log = new List<string>();
int i = 1;
customTiming.Value?.ForEach(value =>
{
if (value.ExecuteType != "OpenAsync")
all_sql.Add(value.CommandString);
if (value.Errored)
err_sql.Add(value.CommandString);
var log = $@"【{customTiming.Key}{i++}】{value.CommandString} Execute time :{value.DurationMilliseconds} ms,Start offset :{value.StartMilliseconds} ms,Errored :{value.Errored}";
all_log.Add(log);
});
//TODO 日誌記錄
//if (err_sql.Any())
// Logger.Error(new Exception("sql異常"), "異常sql:\r\n" + string.Join("\r\n", err_sql), sql: string.Join("\r\n\r\n", err_sql));
//Logger.Debug(string.Join("\r\n", all_log), sql: string.Join("\r\n\r\n", all_sql));
}
}
});
}
}
}
}
運行效果:
Demo源碼
完整的Demo源碼:https://github.com/zhaopeiym/BlogDemoCode/tree/master/Dapper_Demo/DapperDemo
結束
最後給大家推薦一個開源項目quartzui:https://github.com/zhaopeiym/quartzui
基於Quartz.NET 3.0的web管理界面,開箱即用。也可以完美運行在樹莓派上。
docker run -v /fileData/quartzuifile:/app/File --restart=unless-stopped --privileged=true --name quartzui -dp 5088:80 bennyzhao/quartzui:RaspberryPi
運行在普通PC或雲主機上
docker run -v /fileData/quartzuifile:/app/File --restart=unless-stopped --privileged=true --name quartzui -dp 5088:80 bennyzhao/quartzui
新建QQ群工控物聯:995475200