需求場景:前端
1.以前公司有不一樣.net項目組,有的項目是用SqlServer作數據庫,有的項目是用Oracle,後面也有可能會用到Mysql等,並且要考慮後續擴展成主從庫、多庫的需求。(其實無論有沒有這個需求,Dapper的封裝應當像NetDh框架裏封裝的那樣使用);git
2.涉及日誌操做類的設計,須要記錄用戶操做日誌、記錄系統異常日誌等;github
3.涉及緩存操做類的設計,這點不用需求都應該當考慮,不論是小項目的內存緩存仍是大項目中的Redis/Memcache等;sql
4.涉及二次開發模式簡單的設計。由於多個客戶須要同一個項目產品,可是客戶之間對該產品的需求點又有些不同。數據庫
本文先講爲了第1點需求而封裝的數據庫操做類,其它三點在接下來文章中也會介紹。json
Dapper是輕量級高效的框架,它高效緣由是利用Emit技術+各類解析緩存+DataReader。緩存
Dapper能夠在全部Ado.net Providers下工做,包括SQL Server, Oracle, MySQL , SQLite, PostgreSQL, sqlce, firebird 等,這些數據庫操做類都有實現IDbConnection接口。你看源碼會發現,Dapper提供的public方法大都是對IDbConnection的擴展。 app
DapperExtensions是Dapper的第三方插件之一(NetDh框架是用Dapper+DapperExtensions的組合),Dapper經常使用的代碼是 Query<T>(selectSql..)把數據庫獲取的記錄轉成實體類對象,而DapperExtensions是封裝了Dapper,支持諸如 Get<T>(id)、Insert<T>、Update<T>等函數,可讓你不寫sql就能簡單操做數據庫數據。框架
如上圖,NetDh框架是把Dapper(.net4.5.1)目前最新源碼和DapperExtensions源碼合併在同一個程序集,而後添加到解決方案。有源碼就能夠隨便調試,深刻了解Dapper和學習Dapper。ide
在NetDh.DbUtility程序集中,DbHandleBase是個抽象基類,它封裝了「數據庫經常使用的操做函數」,以下圖:
DbHandleBase基類封裝了Dapper+DapperExtensions,若是要實現SqlServerHandle操做類,那麼只要繼承DbHandleBase,而後重寫基類的CreateConnection抽象函數(爲了獲取鏈接對象),便可擁有這些「數據庫經常使用的操做函數」。其它數據庫類型也同樣,很簡單吧。其中,Oracle操做類不使用微軟早期的OracleClient(微軟已經不維護),而是使用Oracle官方ODP.NET(nuget下載 Oracle.ManagedDataAccess.dll),不用再安裝OraInsClient和配置tnsnames.ora。
用了Dapper通常就是不用ExecuteDataTable和ExecuteDataset,爲何DbHandleBase還開放出來,緣由:
(1)winform項目中的Grid常常要用到DataTable的DataView;
(2)sql的參數統一簡單寫法由dapper處理;
(3)Dapper作了各類解析緩存。
貼個DbHandleBase中的ExecuteDataTable的代碼及其註釋:
/* * 說明:winform中常常會用到DataTable的DataView,方便Grid展現與過濾,所以開放ExecuteDataTable和ExecuteDataset; * 若是是B/S系統,建議用以上的Query系列函數。 */ /// <summary> /// 執行sql語句,並返回DataTable(適用於Dapper支持的全部數據庫類型) /// </summary> /// <param name="sql">sql語句</param> /// <param name="param">匿名對象的參數,能夠簡單寫,好比 new {Name="user1"} 便可,會統一處理參數和參數的size</param> public virtual DataTable ExecuteDataTable(string sql, dynamic param = null, int? cmdTimeout = DEFAULTTIMEOUT, IDbTransaction tran = null, CommandType? cmdType = null) { sql = CheckSql(sql); var conn = CreateConnectionAndOpen(); try { //這邊用Dapper的ExecuteReader,統一了函數參數寫法,不用使用SqlParameter。 using (var reader = conn.ExecuteReader(sql, (object)param, tran, cmdTimeout, cmdType)) { var dataTable = DataReaderToDataTable(reader); return dataTable; } } finally { conn.CloseIfOpen(); } }
以SqlServer數據庫爲例,如下直接上代碼,代碼中的註釋很詳細也頗有幫助。
值得一提的是:當取數條件比較複雜或者須要關聯多表時,許多人仍是不寫sql而是喜歡用ORM的Linq表達式。建議簡單的單表CRUD操做不用寫sql,而比較複雜的業務邏輯建議是寫sql,一是sql語法簡單明瞭通用,每批技術員都看得懂;二是你能夠對複雜的業務邏輯明確執行什麼用的sql語句,怎麼樣的執行計劃。若是你Linq寫得複雜,都不知道ORM會給你生成什麼樣的sql出來。
/// <summary> /// NetDh模塊使用示例代碼 /// </summary> public class NetDhExample { #region 用全局靜態變量實現單例。 /// <summary> /// 服務端使用數據庫操做對象,前端不可直接使用 /// </summary> public static DbHandleBase DbHandle { get; set; } //說明:若是你有多庫,好比讀寫分離中的只讀庫,則再定義一個數據庫操做對象便可。 public static DbHandleBase ReadDbHandle { get; set; } #endregion /// <summary> /// 靜態構造函數,只會初始化一次 /// </summary> static NetDhExample() { //初始化數據庫操做對象 DbHandle = new SqlServerHandle(connStr); //若是有多庫,可再new個對象 //ReadDbHandle = new SqlServerHandle(connStrForRead); } /// <summary> /// 模塊使用的示例代碼 /// </summary> public static void TestMain() { #region 數據庫交互(sqlserver+Dapper+DapperExtension) //---------CRUD操做-------- //實體類中的第一個Id或者以Id結尾的字段,會被默認看成主鍵,Dapper不只建議你的表主鍵爲Id或以Id結尾的字段, //並且Dapper默認主鍵字段在數據庫表裏有默認值(好比有設置爲自增加),關於爲何建議用自增加主鍵,能夠翻一下我以前的博客文章《SQL Server索引原理解析》。 //若是表中的主鍵不符合此規定(好比表主鍵是MainKey字段),則須要自定義Map映射,參考如下的「DapperExtensions進階」 var user = DbHandle.Get<TbUser>(1);//這邊1產生的是 where Id=1 的條件 //Get<TbUser>是DapperExtensions的功能,不是Dapper的功能。 //Get<TbUser>這種寫法相似select *,並非好做法,可是它寫法方便,只取一筆影響不大。通常是select你要的字段,而不是select全部字段。具體問題具體分析。 user.Name = "new name"; DbHandle.Update(user); /* 注意若是用實體類去update,就算只更新一個字段,DapperExtension也會生成除了id主鍵以外的全部字段的更新。 * 多餘的更新會增長數據庫開銷,尤爲有非彙集索引字段。所以,建議若是要用此Update函數,則只用於基礎表。 */ var lastInsertId = DbHandle.Insert(user);//返回lastInsertId。由於它生成的語句包含:SELECT CAST(SCOPE_IDENTITY() AS BIGINT) AS [Id] /* DbHandle.Insert(user);是不會報主鍵重複。如下是DbHandle.Insert產生的語法(來自SQL Server Profiler工具), * 不會生成主鍵Id的Insert。由於Dapper默認你的主鍵若是是整形則是KeyType.Identity類型(即默認主鍵字段在數據庫表裏有默認值,好比有設置爲自增加), * DbHandle.Insert(user) DapperExtensions產生的sql語句: exec sp_executesql N'INSERT INTO [TbUser] ([TbUser].[Name], [TbUser].[Age], [TbUser].[Remark], [TbUser].[Department], [TbUser].[CreateTime]) VALUES (@Name, @Age, @Remark, @Department, @CreateTime); SELECT CAST(SCOPE_IDENTITY() AS BIGINT) AS [Id]',N'@CreateTime datetime,@Department nvarchar(200),@Age decimal(6,4),@Name nvarchar(200),@Remark nvarchar(4000)',@CreateTime='2018-06-07 20:05:33.630',@Department=N'D1',@Age=30,@Name=N'new name',@Remark=N'remark1' */ user.Id = 1001; DbHandle.Delete(user); //DbHandle.Delete(user);只和主鍵Id有關。產生的sql:exec sp_executesql N'DELETE FROM [TbUser] WHERE ([TbUser].[Id] = @Id_0)',N'@Id_0 int',@Id_0=1001 DbHandle.Delete(new TbUser() { Id = 1001 }); //--------------------- //1.使用DapperExtensions過濾條件取Id<100的TbUser降序數據列表 var filter = Predicates.Field<TbUser>(f => f.Id, Operator.Lt, 100); var sort = new List<ISort> { Predicates.Sort<TbUser>(f => f.Id, false) };//false降序 var users2 = DbHandle.GetList<TbUser>(filter, sort); //若是須要多個過濾條件須要用到PredicatesGroup嵌套,這是DapperExtensions的功能(不是Dapper原生功能)。 //複雜的sql建議用直接寫sql(以下簡潔版),直接寫複雜sql的優勢:select字段可選、sql執行計劃可控。 //2.使用sql取Id<100的TbUser降序數據列表(簡潔版)能夠指定只取你要的字段Id,Name。簡單明瞭通用。 var uses = DbHandle.Query<TbUser>("select Id,Name from TbUser where Id<@maxId order by Id desc", new { maxId = 100 }); //winform中常常會用到DataTable的DataView,方便Grid展現與過濾,所以開放ExecuteDataTable和ExecuteDataset var table = DbHandle.ExecuteDataTable("select Id,Name from TbUser where Id<100 order by Id desc"); //---------分頁-------- //1.單表分頁(第3頁,一頁10筆) DapperExtension支持單表 var pageUsers = DbHandle.GetPageByModel<TbUser>(null, sort, 2, 10);//參數startPageIndex第1頁是從0開始 //2.sqlserver 本身sql分頁(第3頁,一頁10筆),而且獲取表記錄總數 var pageSql = @" select top 10 * from( select (row_number() over(order by Id))as rowId,* from TbUser where Id<@maxId) as a where a.rowId >20 order by a.rowId; select count(1) from TbUser"; var dataset = DbHandle.ExecuteDataSet(pageSql, new { maxId = 1000 }); //3.封裝的sql分頁,爲了支持不一樣數據庫分頁寫法不一樣(第3頁,一頁10筆),而且獲取表記錄總數,適合較複雜的分頁 var pageSql1 = DbHandle.GetPageSql("select * from TbUser A where A.Id<@maxId", "order by A.Id", 2, 10, "select count(1) from TbUser");//參數startPageIndex第1頁是從0開始 dataset = DbHandle.ExecuteDataSet(pageSql1, new { maxId = 1000 }); //---------多表關聯-------- //select的字段並無對應的實體類時,可用QueryDynamics。DbHandle也支持返回IEnumerable<Hashtable>的QueryHashtables,方便轉爲json格式 var dyObj = DbHandle.QueryDynamics("select A.Name,B.Amount from TbUser A inner join TbOrder B on B.Uid=A.Id where A.Id=@Id", new { Id = 10 }); //---------使用存儲過程-------- //執行存儲過程就是把函數參數CommandType設置爲CommandType.StoredProcedure便可,存儲過程的參數傳遞直接 new {@p=value} #endregion #region DapperExtensions進階--自定義Map映射。 /*項目起初,規範好表設計,通常是不會用到自定義Map映射。若是是現有項目,可酌情考慮*/ //自定義Map,具體參考TbUser.cs裏的代碼說明 //如下這句是初始化,告訴DapperExtensions DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { typeof(TbUserMapper).Assembly }); //DapperExtensions.DapperExtensions.SqlDialect = new DapperExtensions.Sql.SqlServerDialect();//DapperExtensions默認就是SqlServerDialect #endregion } }
好比你的實體類名是TbUser,而對應的數據庫表名是UserTable,或者實體類的某個屬性名和數據庫表字段名不同,則須要Map映射,映射支持如下幾種狀況,看代碼和註釋:
#region 若是須要自定義Map映射,可參考: public class TbUserMapper : ClassMapper<TbUser> { public TbUserMapper() { //1.use different table name Table("UserTable");//把實體類的數據庫表名指定爲UserTable //2.use a custom schema //Schema("not_dbo_schema"); //3.have a custom primary key //KeyType.Assigned說明主鍵在數據庫表無默認值(好比是非自增加的主鍵) //KeyType.Identity說明主鍵在數據庫表有默認值(好比是自增加的主鍵) //Map(x => x.MainKey).Key(KeyType.Assigned); //4.Use a different name property from database column //Map(x => x.Remark).Column("Bar");//把實體類的Remark屬性指定爲數據庫表Bar字段 //5.Ignore this property entirely //Map(x => x.SecretDataMan).Ignore();//忽略實體類中的SecretDataMan字段,即它不是數據庫表字段。 //optional, map all other columns AutoMap(); } //啓動程序時,執行如下定義: //DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { typeof(TbUserMapper).Assembly }); //當你有不少個Model類都有自定義Map時,並且這些自定Map都在同一個程序集,那麼只要上面那一句就能夠了。它會檢索整個Assemble去查找出全部繼承ClassMapper的類。 } #endregion
怎麼讓你自定義的映射生效呢,上面代碼最後一段就是:
//啓動程序時,執行如下定義:
DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { typeof(TbUserMapper).Assembly });
國外有github,國內有碼雲,在國內使用碼雲速度很是快。NetDh框架源碼放在碼雲上: