如何实现Repository模式
导读:本文共10371.5字符,通常情况下阅读需要35分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要: 需求经常写CRUD程序的小伙伴们可能都经历过定义很多Repository接口,分别做对应的实现,依赖注入并使用的场景。有的时候会发现,很多分散的XXXXRepository的逻辑都是基本一致的,于是开始思考是否可以将这些操作抽象出去,当然是可以的,而且被抽象出去的部分是可以不加改变地在今后的任何有此需求的项目中直接引入使用。那么我们本文的需求就是:如何实现一个可... ...
目录
(为您整理了一些要点),点击可以直达。经常写CRUD程序的小伙伴们可能都经历过定义很多Repository
接口,分别做对应的实现,依赖注入并使用的场景。有的时候会发现,很多分散的XXXXRepository
的逻辑都是基本一致的,于是开始思考是否可以将这些操作抽象出去,当然是可以的,而且被抽象出去的部分是可以不加改变地在今后的任何有此需求的项目中直接引入使用。
那么我们本文的需求就是:如何实现一个可重用的Repository
模块。
长文预警,包含大量代码。
实现通用Repository
模式并进行验证。
通用的基础在于抽象,抽象的粒度决定了通用的程度,但是同时也决定了使用上的复杂度。对于自己的项目而言,抽象到什么程度最合适,需要自己去权衡,也许后面某个时候我会决定自己去实现一个完善的Repository
库提供出来(事实上已经有很多人这样做了,我们甚至可以直接下载Nuget包进行使用,但是自己亲手去实现的过程能让你更好地去理解其中的原理,也理解如何开发一个通用的类库。)
总体思路是:在Application
中定义相关的接口,在Infrastructure
中实现基类的功能。
对于要如何去设计一个通用的Repository
库,实际上涉及的面非常多,尤其是在获取数据的时候。而且根据每个人的习惯,实现起来的方式是有比较大的差别的,尤其是关于泛型接口到底需要提供哪些方法,每个人都有自己的理解,这里我只演示基本的思路,而且尽量保持简单,关于更复杂和更全面的实现,GIthub上有很多已经写好的库可以去学习和参考,我会列在下面:
很显然,第一步要去做的是在Application/Common/Interfaces
中增加一个IRepository<T>
的定义用于适用不同类型的实体,然后在Infrastructure/Persistence/Repositories
中创建一个基类RepositoryBase<T>
实现这个接口,并有办法能提供一致的对外方法签名。
IRepository.cs
RepositoryBase.cs
在动手实际定义IRepository<T>
之前,先思考一下:对数据库的操作都会出现哪些情况:
新增实体(Create)
新增实体在Repository
层面的逻辑很简单,传入一个实体对象,然后保存到数据库就可以了,没有其他特殊的需求。
IRepository.cs
RepositoryBase.cs
更新实体(Update)
和新增实体类似,但是更新时一般是单个实体对象去操作。
IRepository.cs
RepositoryBase.cs
删除实体(Delete)
对于删除实体,可能会出现两种情况:删除一个实体;或者删除一组实体。
IRepository.cs
RepositoryBase.cs
获取实体(Retrieve)
对于如何获取实体,是最复杂的一部分。我们不仅要考虑通过什么方式获取哪些数据,还需要考虑获取的数据有没有特殊的要求比如排序、分页、数据对象类型的转换之类的问题。
具体来说,比如下面这一个典型的LINQ查询语句:
可以将整个查询结构分割成以下几个组成部分,而且每个部分基本都是以lambda表达式的方式表示的,这转化成建模的话,可以使用Expression相关的对象来表示:
1.查询数据集准备过程,在这个过程中可能会出现Include/Join/GroupJoin/GroupBy等等类似的关键字,它们的作用是构建一个用于接下来将要进行查询的数据集。
2.Where
子句,用于过滤查询集合。
3.Select
子句,用于转换原始数据类型到我们想要的结果类型。
4.Order
子句,用于对结果集进行排序,这里可能会包含类似:OrderBy/OrderByDescending/ThenBy/ThenByDescending等关键字。
5.Paging
子句,用于对结果集进行后端分页返回,一般都是Skip/Take一起使用。
6.其他子句,多数是条件控制,比如AsNoTracking/SplitQuery等等。
为了保持我们的演示不会过于复杂,我会做一些取舍。在这里的实现我参考了Edi.Wang的Moonglade中的相关实现。有兴趣的小伙伴也可以去找一下一个更完整的实现:Ardalis.Specification。
首先来定义一个简单的ISpecification
来表示查询的各类条件:
并实现这个泛型接口,放在Application/Common
中:
为了在RepositoryBase
中能够把所有的Spcification串起来形成查询子句,我们还需要定义一个用于组织Specification的SpecificationEvaluator
类:
在IRepository
中添加查询相关的接口,大致可以分为以下这几类接口,每类中又可能存在同步接口和异步接口:
IRepository.cs
有了这些基础,我们就可以去Infrastructure/Persistence/Repositories
中实现RepositoryBase
类剩下的关于查询部分的代码了:
RepositoryBase.cs
为了验证通用Repsitory的用法,我们可以先在Infrastructure/DependencyInjection.cs
中进行依赖注入:
用于初步验证(主要是查询接口),我们在Application
项目里新建文件夹TodoItems/Specs
,创建一个TodoItemSpec
类:
然后我们临时使用示例接口WetherForecastController
,通过日志来看一下查询的正确性。
在Get
方法里增加这段逻辑用于观察日志输出:
启动Api项目然后请求示例接口,观察控制台输出:
如何实现Repository模式的详细内容,希望对您有所帮助,信息来源于网络。