原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(6)- EF上下文实例管理
ASP.NET MVC+EF框架+EasyUI实现权限管系列
(开篇) (1):框架搭建 (2):数据库访问层的设计Demo (3):面向接口编程 (4 ):业务逻辑层的封装
前言:通过前面的五篇博客我们已经对权限系统的后台架构进行了详细的说明,那么我再前面的博客中也说到了我们的后台架构还会再改的,我准备这段时间我们继续完善我们的后台,顺便能够把前面的设计架构复习一下,下面我们就开始今天的博客系列,希望大家都能够给予我支持,你们的支持才是我的动力,如果各位感觉写的还可以,请不要吝啬点击一下支持。
1. 解说以前架构的实现
(1)当我们项目进行到这里的时候,我们很有必要解说一下我现在架构所实现的功能,先上张图,然后我详细的解说这些这些文件的含义。
(2)下面我们针对整个项目来解释一下项目类库的用法和作用
1) LYZJ.UserLimitMVC.UI.Portal项目,UI层,负责项目页面的展示,使用EasyUI和MVC4.0来实现的页面。
2) LYZJ.UserLimitMVC.Model类库,用来存放数据表实体的模型,这里放置的时Entity FrameWork模型。也就是使用EF框架来操作数据库。
3) LYZJ.UserLimitMVC.Common类库,专门用来存放一些公用的信息,类。比如Md5加密算法类,文件上传,格式转换等等。
4) LYZJ.UserLimitMVC.DAL类库,数据库访问层的实现,因为我们对数据库的操作也就是增删改查操作,所以我们封装了一个基接口,用于实现对数据库的操作,然后其他的数据库访问层的对象只需要继承自基接口实现对数据库的操作即可,在这里我们也添加了一个工厂(仓储),使用简单工厂管理实例的创建,这样的话我们就把创建实例的这些模式封装到简单工厂里面去了。实现了高内聚,低耦合。
5) LYZJ.UserLimitMVC.IDAL类库,数据库访问接口层,用来存放数据库访问层的接口信息,因为我们对数据库的操作也就那些方法,所以我们封装了一个基接口,然后让其他的对象接口继承自基接口。
6) LYZJ.UserLimitMVC.BLL类库,这是我们对业务逻辑层的实现,和数据库访问层同样的思路,不过我们实现的是对数据库访问层的抽象。
7) LYZJ.UserLimitMVC.IBLL类库,业务逻辑接口层,用来存放的是业务逻辑的接口,实现思路和数据库访问层的思想一致。
(3)在前面我们已经使用到了依赖接口编程,简单工厂模式,这里提醒博客,当我们在做项目的时候我们不要生搬硬套某些设计模式,只有当这些设计模式跟我们的应用场景非常匹配的时候我们在去想办法使用这些设计模式,一般情况下,当我们在写项目的时候,我们自己本来就在用某些设计模式,只是我们自己不知道而已。当我们写完的时候我们发现我们已经使用了某种设计模式。
(4)下面我们继续对数据库访问层进行封装
2.对数据库访问层的在封装
(1)下面我们来分析一下LYZJ.UserLimitMVC.DAL类库下面的RepositoryFactory类,首先我把RepositoryFactory类的代码帖上来,然后解释:
1 namespace LYZJ.UserLimitMVC.DAL 2 3 { 4 5 public static class RepositoryFactory 6 7 { 8 9 public static IUserInfoRepository UserInfoRepository 10 11 { 12 13 get { return new UserInfoRepository(); } 14 15 } 16 17 public static IRoleRepository RoleRepository 18 19 { 20 21 get { return new RoleRepository(); } 22 23 } 24 25 } 26 27 }
(2)这个RepositoryFactory类我们是使用简单工厂来操作的,我们在这里使用简单工厂可以拿到所有的仓储的实例(UserInfoRepository,RoleRepository),其实这个简单工厂从另外一个角度来看的话,这个简单工厂就是我们数据库访问层的一个统一的入口,这是为什么呢?因为从这个简单工厂我们就能够拿到所有的仓储,既然我们能够拿到所有的仓储那么我们就能够操作所有的表,所以说这个仓储(RepositoryFactory)就是我们数据库访问层的统一入口。
(3)下面我们将这个简单工厂在转换成另外一种方式来实现。基于线程内唯一方针。
3.EFContextFactory,DbSession总括
(1)首先我们在LYZJ.UserLimitMVC.DAL类库下面新建DbSession类,那么这个DbSession是干什么的呢?首先我们猜一猜,DB是代表数据库的意思,而Session代表一次会话。
(2) 我们在ASP.NET中请求页面的时候也用Session,Session就代表一次会话的开始,到我们所有的请求结束之后,浏览器关闭之后会话结束,也就是说在他一次请求当中的一个过程称为一个会话,
(3)那么我们新建的这个DbSession是什么意思呢?意思就是我们跟数据库进行一次会话的过程,当我们一个请求过来的时候,我们要跟数据库进行很多次的交互,那我们把很多次交互的这个整体的过程我们称之为一个会话。当我们对数据库进行操作的时候会话开启,当数据库操作结束,请求也结束,那么我们对这个数据库访问的会话也就结束了。
(4)那么我们如果想操作数据库的话,就要通过这个会话来进行操作,在DbSession中就可以拿到我们所有的仓储,然后我们就可以对所有的表进行增删查改操作。当我们对这个表所有的增删查改完成之后,还可以通过DbSession一次性的提交会数据库,因为是一次会话,所以将这次会话中的所有的变化都提交回数据库,然后就实现了批量提交。
4. DbSession一次跟数据库的会话
(1)首先我们要理解DbSession这个类的作用,代表了应用程序跟数据库之间的一次会话,从另外一个角度来说,也是数据库访问层的统一入口。(整个数据库访问层的抽象)。
(2)然后我们书写DbSession类的代码,里面我写了详尽的注释,大家可以看看:
1 namespace LYZJ.UserLimitMVC.DAL 2 3 { 4 5 //一次跟数据库交互的会话,封装了所有仓储的属性,根据DbSession可以拿到仓储的属性 6 7 public class DbSession //代表应用程序跟数据库之间的一次会话,也是数据库访问层的统一入口 8 9 { 10 11 public IDAL.IRoleRepository RoleRepository 12 13 { 14 15 get { return new RoleRepository(); } 16 17 } 18 19 20 21 public IDAL.IUserInfoRepository UserInfoRepository 22 23 { 24 25 get { return new UserInfoRepository(); } 26 27 } 28 29 //代表:当前应用程序跟数据库的绘画内所有的实体的变化,更新会数据库 30 31 public int SaveChanges() 32 33 { 34 35 //调用EF上下文的SaveChanges方法 36 37 return 0; 38 39 } 40 41 } 42 43 }
(3)根据上面的代码注释我们可以看到,调用上下文的SaveChanges方法,那么我们调用上下文中的那个SaveChanges方法呢?下面我们看一下我们之前的EF上下文的处理。
5.EF上下文的处理
(1)首先我们找到LYZJ.UserLimitMVC.DAL类库下面的BaseRepository类里面我们能够看到我们以前是直接使用new来实现的,那么我们整个项目里面上下文的实例会有很多个,我们又遇到了多次,当我们在编程的时候遇到多的时候,一般我们就要想想能不能解决多这个问题。
(2)这里我要说的是EF上下文怎么管理呢?很简单啦,就是要保证线程内唯一,所以这里我们就要进行修改BaseRepository类了。
(3)在这里BaseRepository仓储的职责是什么?他的职责就是帮我们实现了所有子仓储的公共方法(增删查改),他的职责不包含怎么去管理上下文的实例,所以我们不能把这种控制上下文实例的线程内唯一的代码放在这个位置,这就是我们每个类的职责必须唯一,面向对象中的一点就是类的职责必须单一。
(4)下面看一下我们修改后的BaseRepository(仓储),这里我只列出一小部分,因为下面的都没有变化
1 namespace LYZJ.UserLimitMVC.DAL 2 3 { 4 5 /// <summary> 6 7 /// 实现对数据库的操作(增删改查)的基类 8 9 /// </summary> 10 11 /// <typeparam name="T">定义泛型,约束其是一个类</typeparam> 12 13 public class BaseRepository<T> where T : class 14 15 { 16 17 //创建EF框架的上下文 18 19 //EF上下文的实例保证线程内唯一 20 21 //private DataModelContainer db = new DataModelContainer(); 22 23 // 24 25 private DbContext db = EFContextFactory.GetCurrentDbContext(); 26 27 // 实现对数据库的添加功能,添加实现EF框架的引用 28 29 public T AddEntity(T entity) 30 31 { 32 33 //EF4.0的写法 添加实体 34 35 //db.CreateObjectSet<T>().AddObject(entity); 36 37 //EF5.0的写法 38 39 db.Entry<T>(entity).State = EntityState.Added; 40 41 //下面的写法统一 42 43 db.SaveChanges(); 44 45 return entity; 46 47 } 48 49 } 50 51 }
(5)那么我们怎么控制上下文的实例并且要求它是线程内唯一呢?这时候我们不能放到BaseRepository(仓储)中去设置,这时候我们就想到了封装,我们将控制上下文的实例并且要求它是线程内唯一的代码封装到一个公共的类中。这时候怎么做呢?请看下面的做法
(6) 这时候我们看到上面的代码我们不能直接new来实现了(//private DataModelContainer db = new DataModelContainer();),那么我们怎么获取这个实例呢?重点是在这里获取实例的地方必须是公共的,而且还要能够帮我们管理线程内唯一,这时候我们可以想到我们能够通过工厂来实现这个实例,那么我们在这里创建一个EFContextFactory工厂,在这个工厂里面有GetCurrentDbContext()方法来返回实例( private DbContext db = EFContextFactory.GetCurrentDbContext();)。那么这时候我们就需要创建上面的类了。
6.EFContextFactory
(1)这时候我们在LYZJ.UserLimitMVC.DAL类库下面再建立一个EFContextFactory类,在这个类里面含有GetCurrentDbContext方法,下面我解释一下这些代码的实现,我们实现这个方法的代码是:
1 namespace LYZJ.UserLimitMVC.DAL 2 3 { 4 5 public class EFContextFactory 6 7 { 8 9 //帮我们返回当前线程内的数据库上下文,如果当前线程内没有上下文,那么创建一个上下文,并保证 10 11 //上线问实例在线程内部是唯一的 12 13 public static DbContext GetCurrentDbContext() 14 15 { 16 17 //CallContext:是线程内部唯一的独用的数据槽(一块内存空间) 18 19 //传递DbContext进去获取实例的信息,在这里进行强制转换。 20 21 DbContext dbContext = CallContext.GetData("DbContext") as DbContext; 22 23 if (dbContext==null) //线程在数据槽里面没有此上下文 24 25 { 26 27 dbContext = new DataModelContainer(); //如果不存在上下文的话,创建一个EF上下文 28 29 //我们在创建一个,放到数据槽中去 30 31 CallContext.SetData("DbContext", dbContext); 32 33 } 34 35 return dbContext; 36 37 } 38 39 } 40 41 }
(2)首先我们看到我们在定义方法的时候返回的是DbContext,这里需要注意的就是在EF5.0我们使用DbContext,但是在EF4.0的话我们使用ObjectContext,那么DbContext是干什么的呢?他的作用就是帮助我们返回当前线程内的数据库上下文,如果当前线程内没有上下文,那么我们就直接创建一个,并且保证上下文实例在线程内部唯一。
(3)在这里我们用到了CallContext类,那么这个类是干什么的呢?MSDN上面给的说法是这样的
1)资料地址:http://msdn.microsoft.com/zh-cn/library/system.runtime.remoting.messaging.callcontext(VS.80).aspx
2)然后我们找到备注:CallContext是类似于方法调用的线程本地存储区的专用集合对象,并提供对每个逻辑执行线程都唯一的数据槽(数据存储区域),数据槽不在其他逻辑线程上的调用上下文之间共享,当CallContext沿执行代码路径往返传播并且由该路径的各个对象检查时,可以将其对象添加其中。
3)当对另一个AppDomain中的对象进行远程方法调用时,CallContext类将生成一个与该远程调用一起传播的LogicalCallContext实例,只有公开ILogicalThreadAffinatice接口并且存储在CallContext中的对象被在LogicalCallContext中传播的AppDomain外部,不支持此接口的对象不在LogicalCallContext实例中与远程方法调用一起传输。
(4)在CallContext中有两个比较重要的静态方法
1)void SetData(string name,object data),往集合中存入数据。
2) object GetData(string name) 代表取数据,传递一个Key过去取出对应的值。
(5)这时候我们解释一下执行的流程,当线程第一次过来的时候,首先调用这个方法(GetCurrentDbContext)的代码,去数据槽里面看看有没有这个线程,当然了第一次过来肯定没有,这时候我们创建一个,然后存放到dbContext中去了,这时候将new的对象直接返回,而当请求第二次过来的时候,当去数据槽中去取得时候就有信息了,然后不执行判断,直接返回,以后就直接去取就行了。注意:只是当前线程内部。这样就保证了线程内上下文唯一。
Kencery返回本系列开篇