本系列文章将介绍如何在.Net框架下,从零开始搭建一个完成CRUD的Framework,该Framework将具备以下功能,基本实体结构(基于DDD)、基本仓储结构、模块加载系统、工作单元、事件总线(EventBus,具有事件溯源的功能)、以及依赖注入管理系统.
1、简介
本文将通过源码和代码注释和文字说明来解释基本实体结构的构建和基本仓储的构建
2、实战
(1)、基本实体的构建
在OOP的概念之下,对象大致可以分为两类,持久化对象和非持久化对象.本文主要讨论的是持久化对象,即需要写入到数据库或者其他数据容器中的对象,也就是实体(当然这里不是所谓的实体,而是通过OOP技术构建出来的一个实体结构,这个结构需要满足日常开发中绝大多数的业务需求).接下去,就是使用OOP技术来构建这个实体结构.
首先这个实体既然需要写入数据库,那么它必定有一个主键Id.同时这个主键Id可以是任意数据类型,当然用的最多的就是GUID和INT作为主键.前面全局唯一,后者查询效率快.
所以,就有了如下结构:
public interface IEntity<TPrimaryKey> { /// <summary> /// 实体的主键Id /// </summary> TPrimaryKey Id { get; set; } }
其次,以不同数据类型(GUID、int、string)为主键的实体类型,存在一些共有方法,比如需要编写更加语义化的ToString方法,所以当不同类型需要共同的实现的时候,这个时候就需要一层抽象,来处理这层关系,所以就有了如下结构:
[Serializable]
public abstract class Entity<TPrimaryKey> : IEntity<TPrimaryKey> { public TPrimaryKey Id { get; set; } /// <summary> /// 返回当前实体的类型名称+Id的形式 /// </summary> /// <returns></returns> public override string ToString() { return $"{GetType().Name} {Id}"; } }
当然这个结构中可以有任何的共有方法,只要它们能成功的抽象出来,就能写入到里面.
接着,基本实体就出现了,这里我分为两类,一类以int为主键,一类已Guid主键,为别写道两个类中,如下代码:
/// <summary> /// 以int为主键的实体类型 /// </summary> [Serializable] public abstract class Entity: Entity<int> { } /// <summary> /// 以Guid为为主键的实体类型 /// </summary> [Serializable] public abstract class GEntity : Entity<Guid> { }
打上Serializable特性,方便序列化.这里不同的子类使用abstract来实现,也是为了提供各自实体的共有抽象属性(或者方法).到这一步,最最基本的实体抽象构建完毕,但是还没有结束,因为这个结构可以继续优化.使它可以为我们的业务更好的服务.所以需要持久化的实体必定存在一个创建的过程,可能该实体在某些业务下不需要修改、删除或者查询功能,但是它有极大的概率存在一个创建的过程,所以这里需要构建一个实体创建的抽象类,代码如下:
public interface ICreationAudited { /// <summary> /// 创建该实体的用户Id /// </summary> int? CreatorUserId { get; set; } /// <summary> /// 创建当前实体时的时间 /// </summary> DateTime CreationTime { get; set; } } [Serializable] public abstract class CreationAuditedEntity<TPrimaryKey> : Entity<TPrimaryKey>, ICreationAudited { public int? CreatorUserId { get; set; } public DateTime CreationTime { get; set; } /// <summary> /// 构造 当前实体注入内存时,给定创建时间,存在误差,因为从业务点击页面创建到实际生成该实体阶段存在时间差,但是这个时间差可以忽略不计 /// </summary> protected CreationAuditedEntity() { CreationTime = DateTime.Now; } } /// <summary> /// 实体创建 主键为int /// </summary> [Serializable]
public abstract class CreationAuditedEntity : CreationAuditedEntity<int> { }
这里考虑文章大小,Guid的实体创建类型就不实现了,接下去只实现int.
最后实体创建的结构,构建完毕之后,在编写一个需要增删查改所有功能都具备的实体结构,整个实体结构大致就构建完毕了,代码如下:
public interface IDeletionAudited { /// <summary> /// 删除实体的用户Id /// </summary> int? DeleterUserId { get; set; } /// <summary> /// 删除实体的时间 /// </summary> DateTime? DeletionTime { get; set; } } public interface IModificationAudited { /// <summary> /// 最后一次修改实体的用户Id /// </summary> int? LastModifierUserId { get; set; } /// <summary> /// 最后一次修改实体的时间 /// </summary> DateTime? LastModificationTime { get; set; } } public interface IFullAudited : IDeletionAudited, IModificationAudited, ICreationAudited { } [Serializable] public abstract class FullAuditedEntity<TPrimaryKey>:CreationAuditedEntity<TPrimaryKey>,IFullAudited { public int? DeleterUserId { get; set; } public DateTime? DeletionTime { get; set; } public int? LastModifierUserId { get; set; } public DateTime? LastModificationTime { get; set; } } /// <summary> /// 具有增删查该功能的实体结构 主键为int /// </summary> public abstract class FullAuditedEntity : FullAuditedEntity<int> { }
ok,到这里基本的实体结构构建完毕,当然这里你可以随意的扩展,比如构建各种各样的默认的实体类,如主键为string的只具有修改和删除的实体类.可以根据业务的实体特性来动态的扩展.也可以向所有的抽象实体类中添加任意的抽象属性或者方法.比如给Entity添加获取HashCode的共有方法.
(2)、基本仓储结构的构建
关于仓储就不多介绍了,可以自行上网查阅相关的文章,基本仓储结构是依赖于实体结构的。本文将基本Dapper构建一套基本仓储结构.
首先必须有一个仓储接口标识,这个标识本身不具有方法,单单只是一个标识,方便后期实现工作单元和模块加载系统时,辨别出程序集中的仓储类型.如下:
/// <summary> /// 仓储接口 /// </summary> public interface IRepository { }
接着,基于这个接口来构建Dapper通用仓储具有的基本的功能,即增删查改、分页、列表等功能.代码如下:
/// <summary> /// Dapper通用仓储接口 /// </summary> /// <typeparam name="TEntity"></typeparam> /// <typeparam name="TPrimaryKey"></typeparam> public interface IDapperRepository<TEntity, TPrimaryKey> : IRepository where TEntity : class, IEntity<TPrimaryKey> { /// <summary> /// 添加一条实体信息 /// </summary> /// <param name="entity"></param> /// <returns></returns> Task InsertAsync(TEntity entity); /// <summary> /// 删除一条实体信息 /// </summary> /// <param name="entity"></param> /// <returns></returns> Task DeleteAsync(TEntity entity); /// <summary> /// 修改一条实体信息 /// </summary> /// <param name="entity"></param> /// <returns></returns> Task UpdateAsync(TEntity entity); /// <summary> /// 根据主键Id异步获取一条数据信息 /// </summary> /// <param name="id"></param> /// <returns></returns> Task<TEntity> GetAsync(TPrimaryKey id); }