1.NetDh框架开始的需求场景

需求场景:

1.之前公司有不同.net项目组,有的项目是用SqlServer做数据库,有的项目是用Oracle,后面也有可能会用到Mysql等,而且要考虑后续扩展成主从库、多库的需求。(其实不管有没有这个需求,Dapper的封装应当像NetDh框架里封装的那样使用)

2.涉及日志操作类的设计,需要记录用户操作日志、记录系统异常日志等;

3.涉及缓存操作类的设计,这点不用需求都应该当考虑,不管是小项目的内存缓存还是大项目中的Redis/Memcache等;

4.涉及二次开发模式简单的设计。因为多个客户需要同一个项目产品,但是客户之间对该产品的需求点又有些不一样。

本文先讲为了第1点需求而封装的数据库操作类,其它三点在接下来文章中也会介绍。

2.ORM框架Dapper介绍

Dapper是轻量级高效的框架,它高效原因是利用Emit技术+各种解析缓存+DataReader

Dapper可以在所有Ado.net Providers下工作,包括SQL Server, Oracle, MySQL , SQLite, PostgreSQL, sqlce, firebird 等,这些数据库操作类都有实现IDbConnection接口。你看源码会发现,Dapper提供的public方法大都是对IDbConnection的扩展。

DapperExtensions是Dapper的第三方插件之一(NetDh框架是用Dapper+DapperExtensions的组合),Dapper常用的代码是 Query<T>(selectSql..)把数据库获取的记录转成实体类对象,而DapperExtensions是封装了Dapper,支持诸如 Get<T>(id)、Insert<T>、Update<T>等函数,可以让你不写sql就能简单操作数据库数据。

3.数据库操作类-在Dapper+DapperExtensions基础上封装

3.1.总体说明

如上图,NetDh框架是把Dapper(.net4.5.1)目前最新源码和DapperExtensions源码合并在同一个程序集,然后添加到解决方案。有源码就可以随便调试,深入了解Dapper和学习Dapper。

在NetDh.DbUtility程序集中,DbHandleBase是个抽象基类,它封装了“数据库常用的操作函数”,如下图:

DbHandleBase基类封装了Dapper+DapperExtensions,如果要实现SqlServerHandle操作类,那么只要继承DbHandleBase,然后重写基类的CreateConnection抽象函数(为了获取连接对象),即可拥有这些“数据库常用的操作函数”。其它数据库类型也一样,很简单吧。到底有多么简单,我们看下SqlServerHandle类,如下图

一个override CreateConnection搞定,每个数据库的连接对象不一样,因此这个是必要的override。

说明,在NetDh代码中,Oracle操作类不使用微软早期的OracleClient(微软已经不维护),而是使用Oracle官方ODP.NET(nuget下载  Oracle.ManagedDataAccess.dll),不用再安装OraInsClient和配置tnsnames.ora。

另外,用了Dapper(ORM)一般就是不用ExecuteDataTable和ExecuteDataset,为什么DbHandleBase还开放出来,原因:

(1)winform项目中的Grid经常要用到DataTable的DataView;

(2)sql的参数统一简单写法,由dapper处理;

(3)Dapper做了各种解析缓存。

贴个DbHandleBase中的ExecuteDataTable的代码及其注释:

  1. /*
  2. * 说明:winform中经常会用到DataTable的DataView,方便Grid展示与过滤,因此开放ExecuteDataTable和ExecuteDataset;
  3. * 如果是B/S系统,建议用以上的Query系列函数。
  4. */
  5.  
  6. /// <summary>
  7. /// 执行sql语句,并返回DataTable(适用于Dapper支持的所有数据库类型)
  8. /// </summary>
  9. /// <param name="sql">sql语句</param>
  10. /// <param name="param">匿名对象的参数,可以简单写,比如 new {Name="user1"} 即可,会统一处理参数和参数的size</param>
  11. public virtual DataTable ExecuteDataTable(string sql, dynamic param = null, int? cmdTimeout = DEFAULTTIMEOUT, IDbTransaction tran = null, CommandType? cmdType = null)
  12. {
  13. sql = CheckSql(sql);
  14. var conn = CreateConnectionAndOpen();
  15. try
  16. {
  17. //这边用Dapper的ExecuteReader,统一了函数参数写法,不用使用SqlParameter。
  18. using (var reader = conn.ExecuteReader(sql, (object)param, tran, cmdTimeout, cmdType))
  19. {
  20. var dataTable = DataReaderToDataTable(reader);
  21. return dataTable;
  22. }
  23. }
  24. finally
  25. {
  26. conn.CloseIfOpen();
  27. }
  28. }

3.2.调用数据库操作类的示例代码

以SqlServer数据库为例,以下直接上代码,代码中的注释很详细也很有帮助。

值得一提的是:当取数条件比较复杂或者需要关联多表时,许多人还是不写sql而是喜欢用ORM的Linq表达式。建议简单的单表CRUD操作不用写sql,而比较复杂的业务逻辑建议是写sql,一是sql语法简单明了通用,每批技术员都看得懂;二是你可以对复杂的业务逻辑明确执行什么用的sql语句,怎么样的执行计划。如果你Linq写得复杂,都不知道ORM会给你生成什么样的sql出来。

  1. /// <summary>
  2. /// NetDh模块使用示例代码
  3. /// </summary>
  4. public class NetDhExample
  5. {
  6. #region 用全局静态变量实现单例。
  7. /// <summary>
  8. /// 服务端使用数据库操作对象,前端不可直接使用
  9. /// </summary>
  10. public static DbHandleBase DbHandle { get; set; }
  11.  
  12. //说明:如果你有多库,比如读写分离中的只读库,则再定义一个数据库操作对象即可。
  13. public static DbHandleBase ReadDbHandle { get; set; }
  14.  
  15. #endregion
  16. /// <summary>
  17. /// 静态构造函数,只会初始化一次
  18. /// </summary>
  19. static NetDhExample()
  20. {
  21. //初始化数据库操作对象
  22. DbHandle = new SqlServerHandle(connStr);
  23. //如果有多库,可再new个对象
  24. //ReadDbHandle = new SqlServerHandle(connStrForRead);
  25. }
  26. /// <summary>
  27. /// 模块使用的示例代码
  28. /// </summary>
  29. public static void TestMain()
  30. {
  31.  
  32. #region 数据库交互(sqlserver+Dapper+DapperExtension)
  33. //---------CRUD操作--------
  34. //实体类中的第一个Id或者以Id结尾的字段,会被默认当作主键,Dapper不仅建议你的表主键为Id或以Id结尾的字段,
  35. //而且Dapper默认主键字段在数据库表里有默认值(比如有设置为自增长),关于为什么建议用自增长主键,可以翻一下我之前的博客文章《SQL Server索引原理解析》。
  36. //如果表中的主键不符合此规定(比如表主键是MainKey字段),则需要自定义Map映射,参考以下的“DapperExtensions进阶”
  37. var user = DbHandle.Get<TbUser>();//这边1产生的是 where Id=1 的条件
  38. //Get<TbUser>是DapperExtensions的功能,不是Dapper的功能。
  39. //Get<TbUser>这种写法类似select *,并不是好作法,但是它写法方便,只取一笔影响不大。一般是select你要的字段,而不是select所有字段。具体问题具体分析。
  40.  
  41. user.Name = "new name";
  42. DbHandle.Update(user);
  43. /* 注意如果用实体类去update,就算只更新一个字段,DapperExtension也会生成除了id主键之外的所有字段的更新。
  44. * 多余的更新会增加数据库开销,尤其有非聚集索引字段。因此,建议如果要用此Update函数,则只用于基础表。
  45. */
  46.  
  47. var lastInsertId = DbHandle.Insert(user);//返回lastInsertId。因为它生成的语句包含:SELECT CAST(SCOPE_IDENTITY() AS BIGINT) AS [Id]
  48. /* DbHandle.Insert(user);是不会报主键重复。以下是DbHandle.Insert产生的语法(来自SQL Server Profiler工具),
  49. * 不会生成主键Id的Insert。因为Dapper默认你的主键如果是整形则是KeyType.Identity类型(即默认主键字段在数据库表里有默认值,比如有设置为自增长),
  50. * DbHandle.Insert(user) DapperExtensions产生的sql语句:
  51. exec sp_executesql N'INSERT INTO [TbUser] ([TbUser].[Name], [TbUser].[Age], [TbUser].[Remark], [TbUser].[Department], [TbUser].[CreateTime]) VALUES (@Name, @Age, @Remark, @Department, @CreateTime);
  52. 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'
  53. */
  54.  
  55. user.Id = ;
  56. DbHandle.Delete(user);
  57. //DbHandle.Delete(user);只和主键Id有关。产生的sql:exec sp_executesql N'DELETE FROM [TbUser] WHERE ([TbUser].[Id] = @Id_0)',N'@Id_0 int',@Id_0=1001
  58. DbHandle.Delete(new TbUser() { Id = });
  59.  
  60. //---------------------
  61. //1.使用DapperExtensions过滤条件取Id<100的TbUser降序数据列表
  62. var filter = Predicates.Field<TbUser>(f => f.Id, Operator.Lt, );
  63. var sort = new List<ISort> { Predicates.Sort<TbUser>(f => f.Id, false) };//false降序
  64. var users2 = DbHandle.GetList<TbUser>(filter, sort);
  65. //如果需要多个过滤条件需要用到PredicatesGroup嵌套,这是DapperExtensions的功能(不是Dapper原生功能)。
  66. //复杂的sql建议用直接写sql(如下简洁版),直接写复杂sql的优点:select字段可选、sql执行计划可控。
  67.  
  68. //2.使用sql取Id<100的TbUser降序数据列表(简洁版)可以指定只取你要的字段Id,Name。简单明了通用。
  69. var uses = DbHandle.Query<TbUser>("select Id,Name from TbUser where Id<@maxId order by Id desc", new { maxId = });
  70.  
  71. //winform中经常会用到DataTable的DataView,方便Grid展示与过滤,因此开放ExecuteDataTable和ExecuteDataset
  72. var table = DbHandle.ExecuteDataTable("select Id,Name from TbUser where Id<100 order by Id desc");
  73.  
  74. //---------分页--------
  75. //1.单表分页(第3页,一页10笔) DapperExtension支持单表
  76. var pageUsers = DbHandle.GetPageByModel<TbUser>(null, sort, , );//参数startPageIndex第1页是从0开始
  77.  
  78. //2.sqlserver 自己sql分页(第3页,一页10笔),并且获取表记录总数
  79. var pageSql = @" select top 10 * from(
  80. select (row_number() over(order by Id))as rowId,* from TbUser where Id<@maxId) as a where a.rowId >20 order by a.rowId;
  81. select count(1) from TbUser";
  82. var dataset = DbHandle.ExecuteDataSet(pageSql, new { maxId = });
  83.  
  84. //3.封装的sql分页,为了支持不同数据库分页写法不同(第3页,一页10笔),并且获取表记录总数,适合较复杂的分页
  85. var pageSql1 = DbHandle.GetPageSql("select * from TbUser A where A.Id<@maxId", "order by A.Id", , , "select count(1) from TbUser");//参数startPageIndex第1页是从0开始
  86. dataset = DbHandle.ExecuteDataSet(pageSql1, new { maxId = });
  87. //---------多表关联--------
  88. //select的字段并没有对应的实体类时,可用QueryDynamics。DbHandle也支持返回IEnumerable<Hashtable>的QueryHashtables,方便转为json格式
  89. 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 = });
  90.  
  91. //---------使用存储过程--------
  92. //执行存储过程就是把函数参数CommandType设置为CommandType.StoredProcedure即可,存储过程的参数传递直接 new {@p=value}
  93.  
  94. #endregion
  95.  
  96. #region DapperExtensions进阶--自定义Map映射。
  97. /*项目起初,规范好表设计,一般是不会用到自定义Map映射。如果是现有项目,可酌情考虑*/
  98. //自定义Map,具体参考TbUser.cs里的代码说明
  99.  
  100. //以下这句是初始化,告诉DapperExtensions
  101. DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { typeof(TbUserMapper).Assembly });
  102. //DapperExtensions.DapperExtensions.SqlDialect = new DapperExtensions.Sql.SqlServerDialect();//DapperExtensions默认就是SqlServerDialect
  103.  
  104. #endregion
  105.  
  106. }
  107. }

3.3.DapperExtensions进阶--自定义Map映射

比如你的实体类名是TbUser,而对应的数据库表名是UserTable,或者实体类的某个属性名和数据库表字段名不一样,则需要Map映射,映射支持以下几种情况,看代码和注释:

  1. #region 如果需要自定义Map映射,可参考:
  2. public class TbUserMapper : ClassMapper<TbUser>
  3. {
  4. public TbUserMapper()
  5. {
  6. //1.use different table name
  7. Table("UserTable");//把实体类的数据库表名指定为UserTable
  8.  
  9. //2.use a custom schema
  10. //Schema("not_dbo_schema");
  11.  
  12. //3.have a custom primary key
  13. //KeyType.Assigned说明主键在数据库表无默认值(比如是非自增长的主键)
  14. //KeyType.Identity说明主键在数据库表有默认值(比如是自增长的主键)
  15. //Map(x => x.MainKey).Key(KeyType.Assigned);
  16.  
  17. //4.Use a different name property from database column
  18. //Map(x => x.Remark).Column("Bar");//把实体类的Remark属性指定为数据库表Bar字段
  19.  
  20. //5.Ignore this property entirely
  21. //Map(x => x.SecretDataMan).Ignore();//忽略实体类中的SecretDataMan字段,即它不是数据库表字段。
  22.  
  23. //optional, map all other columns
  24. AutoMap();
  25. }
  26. //启动程序时,执行以下定义:
  27. //DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { typeof(TbUserMapper).Assembly });
  28. //当你有很多个Model类都有自定义Map时,而且这些自定Map都在同一个程序集,那么只要上面那一句就可以了。它会检索整个Assemble去查找出所有继承ClassMapper的类。
  29.  
  30. }
  31. #endregion

怎么让你自定义的映射生效呢,上面代码最后一段就是:

//启动程序时,执行以下定义:
DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { typeof(TbUserMapper).Assembly });

3.4.源码

国外有github,国内有码云,在国内使用码云速度非常快。NetDh框架源码放在码云上:

https://gitee.com/donghan/NetDh-Framework

1.NetDh框架之数据库操作层--Dapper简单封装,可支持多库实例、多种数据库类型等(附源码和示例代码)的更多相关文章

  1. 3.NetDh框架之缓存操作类和二次开发模式简单设计(附源码和示例代码)

    前言 NetDh框架适用于C/S.B/S的服务端框架,可用于项目开发和学习.目前包含以下四个模块 1.数据库操作层封装Dapper,支持多种数据库类型.多库实例,简单强大: 此部分具体说明可参考博客: ...

  2. 2.NetDh框架之简单高效的日志操作类(附源码和示例代码)

    前言 NetDh框架适用于C/S.B/S的服务端框架,可用于项目开发和学习.目前包含以下四个模块 1.数据库操作层封装Dapper,支持多种数据库类型.多库实例,简单强大: 此部分具体说明可参考博客: ...

  3. 从零开始编写自己的C#框架(12)——T4模板在逻辑层中的应用(一)(附源码)

    对于T4模板很多朋友都不太熟悉,它在项目开发中,会帮我们减轻很大的工作量,提升我们的开发效率,减少出错概率.所以学好T4模板的应用,对于开发人员来说是非常重要的. 园子里对于T4模板的介绍与资料已经太 ...

  4. 使用Dapper.Contrib 开发.net core程序,兼容多种数据库

    关于Dapper的介绍,我想很多人都对它有一定的了解,这个类似一个轻型的ORM框架是目前应用非常火的一个东西,据说各方面的性能都不错,而且可以支持多种数据库,在开始介绍这个文章之前,我花了不少功夫来学 ...

  5. SQL 横转竖 、竖专横 (转载) 使用Dapper.Contrib 开发.net core程序,兼容多种数据库 C# 读取PDF多级书签 Json.net日期格式化设置 ASPNET 下载共享文件 ASPNET 文件批量下载 递归,循环,尾递归 利用IDisposable接口构建包含非托管资源对象 《.NET 进阶指南》读书笔记2------定义不可改变类型

    SQL 横转竖 .竖专横 (转载)   普通行列转换 问题:假设有张学生成绩表(tb)如下: 姓名 课程 分数 张三 语文 74 张三 数学 83 张三 物理 93 李四 语文 74 李四 数学 84 ...

  6. Dapper.Contrib 开发.net core程序,兼容多种数据库

    Dapper.Contrib 开发.net core程序,兼容多种数据库 https://www.cnblogs.com/wuhuacong/p/9952900.html 使用Dapper.Contr ...

  7. 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生

    [转].NET(C#):浅谈程序集清单资源和RESX资源   目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...

  8. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

  9. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(2)-easyui构建前端页面框架[附源码]

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(2)-easyui构建前端页面框架[附源码] 开始,我们有了一系列的解决方案,我们将动手搭建新系统吧. 用 ...

随机推荐

  1. Docker安装Oracle12C,导入dmp文件出现ORA-12170错误

    oracle版本为 sath89/oracle-12c oracle基本信息 hostname: localhost port: 1521 sid: xe username: system passw ...

  2. UVA12633 Super Rooks on Chessboard

    题目描述 题解: 第一眼满眼骚操作,然后全部否掉. 然后屈服于题解,才发现这题这么执掌. 首先,如果这个东西是普通的车,那我们可以记录一下$x,y$的覆盖情况,然后减一下; 但是这个可以斜着走. 所以 ...

  3. 快速安装zabbix

    环境:CentOS 7.x 数据库mysql已事先安装 1.配置epel源 wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/r ...

  4. 20. Valid Parentheses (python版)

    Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the inpu ...

  5. Linux 基本操作指南

    Linux基本操作 1. su  切换用户   2.exit 退出当前登录用户 3.useradd 用户名  -m  在home目录下 创建一个和用户名同名的目录,并添加一个用户 (有root权限才能 ...

  6. C语言学习10

    判断三角形的类型 根据输入的三角形的三条边判断三角形的类型,并输出它的面积. #include <stdio.h> #include <math.h> void judge_1 ...

  7. iframe的操作switch_to_frame使用方法.

    一.frame和iframe区别 Frame与Iframe两者可以实现的功能基本相同,不过Iframe比Frame具有更多的灵活性. frame是整个页面的框架,iframe是内嵌的网页元素,也可以说 ...

  8. Postfix telnet www.azengna.com 25 Connection Refused 但是localhost连接成功

    修改配置文件 vi /etc/postfix/main.cf 原先配置信息 .... inet_interfaces = all #inet_interfaces = $myhostname,loca ...

  9. 【HIHOCODER 1605】小Hi的生成树计数

    描述 小Hi最近对生成树(包含所有顶点的联通无环子图.)非常的感兴趣,他想知道对于特定的简单平面无向图是不是存在求生成树个数的简单方法. 小Hi定义了这样的图:一个以{0,1,2--n}为顶点的图,顶 ...

  10. shelve -- 用来持久化任意的Python对象

    这几天接触了Python中的shelve这个module,感觉比pickle用起来更简单一些,它也是一个用来持久化Python对象的简单工具.当我们写程序的时候如果不想用关系数据库那么重量级的东东去存 ...