千家信息网

OSS.Core基于Dapper封装(表达式解析+Emit)仓储层的构思及实现

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,最近趁着不忙,在构思一个搭建一个开源的完整项目,至于原因以及整个项目框架后边文章我再说明。既然要起一个完整的项目,那么数据仓储访问就必不可少,这篇文章我主要介绍这个新项目(OSS.Core)中我对仓储
千家信息网最后更新 2026年11月01日OSS.Core基于Dapper封装(表达式解析+Emit)仓储层的构思及实现

最近趁着不忙,在构思一个搭建一个开源的完整项目,至于原因以及整个项目框架后边文章我再说明。既然要起一个完整的项目,那么数据仓储访问就必不可少,这篇文章我主要介绍这个新项目(OSS.Core)中我对仓储层的简单思考和实现过程(当前项目还处在搭建阶段),主要集中在以下几个方面:

1. 数据仓储层的需求

2. ORM框架选择

3. OSS.Core仓储层设计实现

4. 调用示例

下边的实现部分中可能需要你对.NET的 泛型,委托,扩展,表达式等有一个基础了解。正是因为这些语言特性,方便我们对操作共性的抽取统一。

一. 数据仓储层需求

  既然是一个完整的项目,数据访问是其最基本的部分,同时,数据访问也是整个项目最容易出现瓶颈的地方。在我的划分中,其承担的角色是负责整个数据的输入输出,不仅仅是针对单数据库(有时甚至多库),有时还需要完成一级缓存的实现,给逻辑层提供最基础的数据支撑。

  业务永远是在变化的,那么项目也要具备快速演进的能力,所以我希望数据层能够保持相对的简单,在结构上尽量减少复杂的耦合查询,在性能上尽量减少不必要的消耗,例如反射的大量使用。同时针对每个业务对象完成数据库层面基本的CRUD统一封装实现。如果有需要的时候还能在最少的改动下加入缓存的更新。(对于如何实现不同模块不同缓存存储策略,像Redis,Memcached会在后边文章介绍)

  同时,对于一个稍微有点规模的项目来说,解决数据库访问的最快速做法就是实现读写分离,所以,我希望这个框架能够在一开始在底层就实现了读写分离的支持,以避免后期再重头对业务代码的大量修改。  

二. ORM 框架选择

  当然,如果为了简单和性能,直接ADO.NET连接理论上来说是比较高效的做法,不过这样会造成大量的重复操作逻辑代码,同时也会造成代码的散乱,增加维护复杂度。作为技术人员,不仅需要解决业务问题提高效率,同时也要提高自己的效率,所以我会选择一个ORM框架来完成部分基础工作。

  当前在.NET体系下,开源的ORM框架很多,如:Entityframework,NHibernate,iBATIS.NET,Dapper等等,各有特色,基于前面我说的,保证效率的同时,兼顾简单还能最大程度减少性能的损耗,并且提供.net standard标准库下的支持。这里对比之后我选择Dapper这个半自动化的ORM作为仓储层的基础框架,选择原因如下:

  1. 其结构简单,整个封装主要集中Dapper.cs文件中,体积很小

2. 封装功能简单强大,对原生SQL的支持上很灵活

    这点几乎完胜其他框架,无需任何多余的设置,同时基本上你可调用所有原生ADO.NET的功能,sql语句完全自己掌控,却又无需关心command的参数赋值,以及结果实体转换等。

  3. 性能上的高效

    很多ORM的实体映射通过反射来完成,这点上Dapper再次展现其魅力,在Commond参数赋值,以及实体转换等关键模块,使用了Reflection.Emit功能,间接实现了MSIL编译层面的赋值实现,之所以说间接,是因为其本身代码还需要编译器生成IL代码。在运行时根据类型属性动态创建赋值委托方法。

三. OSS.Core仓储层设计实现

  通过Dapper可以实现在数据库访问部分一层简单的封装,不过我依然需要手动编写不少的sql语句,同时还要进行参数化的处理,包括数据的读写分离等。那么这些功能的实现我将在OSS.Core.RepDapper中完成,为了方便理解,先贴出一个简单的封装后的方法调用传输流程:

  在这个图里展示一个简单的方法调用流程,围绕这张图的几个核心部分,我分别介绍下:

  1. 接口设计

  因为我希望这个是完整的示例项目,所以后边希望能够兼容不同数据库,因此对外的仓储访问都基于接口调用。当然如果你的项目根本没有切换数据库的需求,我更建议去掉这一环节,直接在基类中实现单例模式,业务逻辑层直接调用。

  图中可以看到接口层独立于实现部分,我将具体业务实体模型和接口 单独放在了OSS.Core.DomainMos 类库中,一方面是为了实体模型在各模块中的共用,另一方面解耦业务逻辑层(Services)和仓储层(Reps)之间的依赖关系。

  同时一个项目中数据库访问代码多数都会以CRUD为主,所以这里我定义了一个基础接口(IBaseRep),其包含的方法主要有(表达式部分在后边介绍):

  具体的业务数据接口继承至基础接口就好,其中表达式部分是我自己做了一个封装,后边会简单介绍。

  2. 仓储基类实现(BaseRep)

  首先,如图所示,我们实现了读写分离的两个扩展,其实最终都会经过Excute方法,那么这里展示下方法的具体实现:

  可以看到在这个方法提供了一个针对IDbConnection的委托,提供调用层自由使用Dapper方法的同时,统一了数据访问方法入口,便于日志记录,和排查。

  其次,在很多项目中会出现用户和订单在不同库中的这类情况,因为涉及到分库的情况,所以需要子类中能有修改连接串能力,那么这里我通过构造函数的形式,提供了两个可空参数:

  可以看到,如果子类中定义了自己的连接串,则以子类自定义为主,否则走默认的连接信息。

  

  最后,我们也实现了针对基础接口方法的具体实现,举一示例:

  同时,为了保证子类中能够加入缓存处理,所以采用了虚方法(virtual)的形式,保证子类能够重写。

  3. 基于Connection的扩展

  这个地方主要分为两个部分,a. 表达式的解析,以及参数化的处理 b. 扩展Connection的Insert,Update...等Dapper没有扩展的方法:

  a. 熟悉Expression表达式的朋友应该比较了解,表达式本身是一个树形接口,根据不同的类型,可以不断的解析其子表达式,直到不具备继续解析的可能。所以这个就很简单就是递归的不断迭代,根据其不同的NodeType可以组装不同的sql元素,因为代码较长,可以参见github下的SqlExpressionVisitor.cs类,其中参数的赋值部分,没有采用反射,而是使用的反射发射,代码详见SqlParameterEmit.cs

  b. 有了表达式的扩展之后,就可以获取对应的sql和参数,通过this扩展Connection方法即可,代码见ConnoctionExtention.cs

  

四. 调用示例

  1. 我们定义一个简单UserInfoMo实体(包含mobile等属性)

  2. 定义接口 IUserInfoRep: IBaseRep

  3. 定义实现类 UserInfoRep : BaseRep, IUserInfoRep

  在不添加其他代码的基础上,我们就可以完成下面的调用:



数据 方法 项目 同时 仓储 代码 接口 部分 表达式 业务 基础 框架 不同 参数 数据库 封装 实体 子类 选择 功能 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网 科技公司 万易互联网科技 为想互联网科技 朗涯互联网科技 广州善班互联网科技 佰商互联网科技有限公司 互联网科技类展会 佩信高科技互联网 湖南洣水互联网科技有限公司扣款 互联网之后的科技是什么 中国的互联网科技企业的崛起 小米互联网科技园设置 互联网企业和科技型企业 知域互联网科技有限公司大骗子 互联网方面的科技前沿 本届互联网大会上的黑科技 吉林省码上互联网科技电话 互联网科技企业商标注意事项 米泉互联网科技专业怎么样 第三届互联网大会安防黑科技 西安纸贵互联网科技有限公司 互联网科技股票 卫星互联网科技股票 中盈科技互联网信息有限公司 互联网科技博主是什么意思 互联网软件和科技 黑科技闪耀互联网之光 四川慕妮娅互联网科技有限公司 互联网金融科技名词解释 宁波跨境商互联网科技有限公司
0