Dapper的正確使用姿勢

来源:https://www.cnblogs.com/zhaopei/archive/2019/05/20/dapper.html
-Advertisement-
Play Games

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

結束

最後給大家推薦一個開源項目quartzuihttps://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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • UDP協議: 1、python中基於udp協議的客戶端與服務端通信簡單過程實現 2、udp協議的一些特點(與tcp協議的比較) 3、利用socketserver模塊實現udp傳輸協議的併發通信 一、UDP協議:OSI七層協議中的傳輸協議的一種(另外一種tcp協議),他們都是一種埠協議 與TCP協議 ...
  • List集合有序、元素可重覆。以元素的添加順序作為集合的排列順序,用下標索引集合中的元素。 List因為使用下標索引元素,所以元素可重覆。Set使用元素本身來索引,所以元素不能重覆。 List的繼承關係: List繼承了Collection的所有方法,也有自身的一些方法(下標操作): void ad ...
  • 1.什麼是列表? 列表是由一組按特定順序排列的元素組成。 2.如何表示? 在Python中用方括弧([ ])來表示列表。慄子如下: 控制台列印如下: 沒錯,它將列印列表的內部表示,包括方括弧。 3.如何去訪問列表元素? console(控制台): 所以說訪問列表的元素,可以在列表名後加方括弧,方括弧 ...
  • hasNextInt() :判斷是否還有下一個輸入項,其中Xxx可以是Int,Double等。如果需要判斷是否包含下一個字元串,則可以省略Xxx nextInt(): 獲取下一個輸入項。Xxx的含義和上個方法中的Xxx相同,預設情況下,Scanner使用空格,回車等作為分隔符 public stat ...
  • 1. 是否保證線程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保證線程安全; 2. 底層數據結構: Arraylist 底層使用的是Object數組;LinkedList 底層使用的是雙向鏈表數據結構(JDK1.6之前為迴圈鏈表,JDK1.7取消了迴圈。註意雙向鏈表和 ...
  • 1. 前言 對WPF來說ContentControl和 "ItemsControl" 是最重要的兩個控制項。 顧名思義,ItemsControl表示可用於呈現一組Item的控制項。大部分時候我們並不需要自定義ItemsControl,因為WPF提供了一大堆ItemsControl的派生類:Headere ...
  • 在SQL語句查詢過程中,Sqlserver支持使用LEFT()、RIGHT()、SUBSTRING()等幾個函數對字元串進行截取操作,其中Left函數表示從開始字元向後截取多少個字元,Right函數表示從最後位置向前截取多少個字元,SUBSTRING()則可指定截取的起始位置以及截取長度。此文著重介 ...
  • 在C#語言程式開發過程中,很多時候需要對字元串對象的前後空格進行去除,此時就需要使用到Trim()方法來實現這個功能,Trim()方法可以快速去除字元串前端和後端的所有空格。 例如有個字元:string str=" Abc "; 則需要去除字元串前後空格的話,則可以採用:str=str.Trim() ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...