接触abp也快一年了,有过大半年的abp项目开发经验,目前项目中所用的abp框架版本为0.10.3,最新的abp框架已经到了1.4,并且支持了asp.net core。关于abp框架有哪些特性、有什么好处我这里就不讲了。如果你对abp不太了解请先去 Abp官网 了解或者参考我的学习之路里面的链接。在这系列的文章里面我会把我在实战中的经验、碰到的坑分享出来。希望跟大家一起学习讨论。
学习之路
一年前在博客园里面看到了阳光铭睿 的 http://www.cnblogs.com/mienreal/p/4528470.html 一系列文章使我对abp框架产生了兴趣。从此开始了abp框架的探索之路。后来在找资料的过程中发现 tkb至简 的博客写的也非常不错,他也翻译了官网的英文文档 http://www.cnblogs.com/farb/category/762156.html ,要入门的话看下这一系列的文章非常好。如果想研究源码可以参考下 http://www.cnblogs.com/1zhk/category/798531.html 这一系列的文章。
Abp在github上的源码地址为:https://github.com/aspnetboilerplate。
框架概述
这个框架版本主要用的技术有 mvc5.0、web api、EF 6.1 。
一、整体框架概述
Topeveyr.Core:主要存放业务实体以及仓储接口。
Application文件夹:这里面有两个应用服务层,当时是想做读写分离的,一个读一个写。但是底层的数据访问层没有实现读写分离。(全完实现读写分离还没找到解决方案)
Topevery.EntityFramework.Oracle:主要存放实体映射文件、仓储实现类。
Topevery.Web:web界面。
Topevery.WebApi:api接口。
Topevery.Infrastructure:框架中跟业务无关的公共类。
Topevery.WF.Framework:流程业务相关项目。
Topevery.Abp:封装了abp类的公共类。
TimeServices:定时服务类库。
Dapper:一款轻量级orm框架。主要是为了支持EF操作Oracled包而加进来的。如果是其他数据库可以忽略该项目。为了更好的统一,以后应该全部换成存储过程。
Zero文件夹:这里面的三个项目分别对应abp的应用服务层、核心层、数据访问层。这个项目主要用来操作一些基础信息表以及公共模块。比如,用户、角色、权限、部门等。
二、整体框架设计
整体框架设计里面,我觉得“模块”的比较重要,这里就讲一讲我对模块的理解。每个单独类库或者应用程序即是一个模块。当需要用到Ioc容器、abp启动配置或者是在应用程序启动的时候做一些设置就需要用到abp里面的模块。模块类可以被依赖。模块类在应用程序启动的时候被执行,并且按依赖的关系进行执行。即没有被依赖的模块最后执行。模块有四个方法,PreInitialize()、Initialize()、PostInitialize()、Shutdown()。前三个方法是在应用程序启动的时候运行的,最后一个方法是在应用程序关闭的时候运行的。模块方法的执行顺序是先执行完所有模块的PreInitialize(),再执行所有模块的Initialize(),最后执行所有模块的PostInitialize()。
整体框架模块的依赖设计图如下:
三、框架详细设计
本框架大概思路是根据abp的示例代码而来。我只是参照原来的框架做了一些扩展。相同的框架设计就不讲了。讲一讲不同之处。在本框架里面的Zero项目跟官方Abp.Zero不同。Abp.Zero里面基本上都是提供抽象类,要具体实例化必须得在相应的业务类库里面继承才能用。本框架里的Zero项目直接提供对外的服务接口。Topevery.Zero是应用服务接口。Topevery.Zero.Core存放实体仓储接口。Topevery.Zero.EntityFramework.Oracel是数据访问抽象层,该类库里面有继承了AbpDbContext的抽象类。由于本框架用的是Oracle,目前还没有找到EF操作自增长类型的解决方案。所以目前插入数据的时候解决思路是先获取序列再插入数据。
获取序列的话有两种解决方案。一种是在仓储的基类里面添加公共方法,这种方案的优势可以很好的集成依赖注入,并且可以做到事务控制。但这种方案的劣势是所有的实体类都必须有对应的仓储基类。另外一种方案是新增一个数据访问DbContext。这个方案的优势在于可以在应用服务的基类里面添加一个获取序列的公共方法。劣势是序列的获取和其他数据的操作不能一起做事务控制。
我在框架里面采用的是第二种方案。整体思路是在 Topevery.Zero.EntityFramework.Oracle 项目中添加 IDbContextHelper 接口,该接口有两个获取序列的方法。在Topevery.EntityFramework.Oracle 项目中添加 DbContextHelper,该类继承 IDbContextHelper 接口,并且有个 DbContext 的变量,用来访问数据库。为啥要这样设计?因为解耦。所有 Zero 里面的的项目都不需要直接引用具体的DbContextHelper而只要引用接口即可,具体的接口实现是在运行的时候注入。具体代码如下:
1 public interface IDbContextHelper 2 { 3 /// <summary> 4 /// 获取序列值 5 /// </summary> 6 /// <typeparam name="T"></typeparam> 7 /// <param name="sequenceName"></param> 8 /// <returns></returns> 9 T GetSequenceValueByName<T>(string sequenceName) where T : struct; 10 11 /// <summary> 12 /// 获取序列值(默认int) 13 /// </summary> 14 /// <param name="sequenceName"></param> 15 /// <returns></returns> 16 int GetSequenceValueByName(string sequenceName); 17 }
1 public class DbContextHelper:IDbContextHelper,ITransientDependency 2 { 3 /// <summary> 4 /// 数据访问类 5 /// </summary> 6 private volatile DbContext _dbContext ; 7 8 /// <summary> 9 /// 锁帮助对象 10 /// </summary> 11 private readonly object _lockObject = new object(); 12 13 /// <summary> 14 /// 15 /// </summary> 16 private DbContext TopeveryDbContext 17 { 18 get 19 { 20 if (_dbContext == null) 21 { 22 lock (_lockObject) 23 { 24 if (_dbContext == null) 25 _dbContext = IocManager.Instance.Resolve<TopeveryDbContext>(); 26 } 27 } 28 return _dbContext; 29 } 30 } 31 32 /// <summary> 33 /// 得到序列的字符串 34 /// </summary> 35 /// <param name="sequenceName"></param> 36 /// <returns></returns> 37 private string GetSequenceSqlByName(string sequenceName) 38 { 39 return "select " + sequenceName + ".nextval from dual"; 40 } 41 42 /// <summary> 43 /// 获取序列值 44 /// </summary> 45 /// <typeparam name="T"></typeparam> 46 /// <param name="sequenceName"></param> 47 /// <returns></returns> 48 public T GetSequenceValueByName<T>(string sequenceName) where T : struct 49 { 50 string sql = GetSequenceSqlByName(sequenceName); 51 return TopeveryDbContext.Database.SqlQuery<T>(sql).First(); 52 } 53 54 55 /// <summary> 56 /// 获取序列值(默认int) 57 /// </summary> 58 /// <param name="sequenceName"></param> 59 /// <returns></returns> 60 public int GetSequenceValueByName(string sequenceName) 61 { 62 return GetSequenceValueByName<int>(sequenceName); 63 } 64 }
今天就讲这么多了。
源码地址:https://github.com/yuanbeier/JMGF.git