在本系列的第一篇随笔《Entity Framework 实体框架的造成之旅--基于泛型的仓储模式的实体框架(1)》中介绍了Entity Framework 实体框架的一些基础知识,以及构建了一个简单的基于泛型的仓储模式的框架,例子也呈现了一个实体框架应用的雏形,本篇继续介绍这个主题,继续深化介绍Entity Framework 实体框架的知识,以及持续优化这个仓储模式的实体框架,主要介绍业务逻辑层的构建,以及利用Unity和反射进行动态的对象注册。html
咱们从上篇例子,能够看到这个随笔介绍的仓储模式的实体框架结构以下所示。数据库
但实际上上篇随笔的例子是有点理想化的了,由于咱们知道,【ADO.NET实体数据模型】生成的EDMX文件实质上自动生成了数据访问的上下文SqlserverContext,以及几个表的实体类,具体的效果以下所示。缓存
咱们理想化的把它放到DAL目录,Entity目录下,其实是不能够的,至少是有冲突的。框架
那么咱们应该如何处理,才能比较合理的处理这些自动生成的内容呢?另外咱们已经把它上升了一层到业务层,具体的BLL分层如何处理数据访问对象的呢,经过什么方式构建数据访问对象?带着这些问题,咱们再来一步步分析这个框架的内容。异步
为了给实体类友好的名称,咱们顺便把表名的前缀移除了,如EDMX的图形以下所示。函数
为了比较好的利用EDMX文件的代码生成,咱们把这个文件总体性的移动到了Entity目录下,以下所示。post
这样至关于把数据访问的上下文,以及实体类的代码所有移动到Entity命名空间里面去了,虽然可能感受不太好,可是咱们先让它跑起来,具体的细节后面在优化完善。优化
咱们再来关注下业务逻辑层(包括业务逻辑接口层),和数据访问层相似,咱们把它构建以下所示。this
1)业务逻辑接口层编码
/// <summary> /// 业务逻辑层基类接口 /// </summary> /// <typeparam name="T">实体对象类型</typeparam> public interface IBaseBLL<T> where T : class { T Get(object id); IList<T> GetAll(Expression<Func<T, bool>> whereCondition); IList<T> GetAll(); }
2)业务逻辑层实现
/// <summary> /// 业务逻辑基类 /// </summary> /// <typeparam name="T">实体对象类型</typeparam> public abstract class BaseBLL<T>: IBaseBLL<T> where T : class { protected IBaseDAL<T> baseDAL { get; set; } public BaseBLL(IBaseDAL<T> dal) { this.baseDAL = dal; } public T Get(object id) { return baseDAL.Get(id); } public IList<T> GetAll(Expression<Func<T, bool>> whereCondition) { return baseDAL.GetAll(whereCondition); } public IList<T> GetAll() { return baseDAL.GetAll(); } }
3)业务对象类的逻辑接口层
/// <summary> /// 城市的业务对象接口 /// </summary> public interface ICityBLL : IBaseBLL<City> { }
4)业务对象的逻辑层实现
/// <summary> /// 城市的业务对象 /// </summary> public class CityBLL : BaseBLL<City> { protected ICityDAL dal; public CityBLL(ICityDAL dal) : base(dal) { this.dal = dal; } }
上面基本上完整的阐述了业务逻辑层的实现了,不过咱们看到一个问题,就是不论是逻辑层基类,仍是具体业务对象的逻辑对象,都没有默认构造函数,咱们不能使用new进行对象的建立!
这是一个严重的问题,那么咱们如何才能规避这个问题,可以使咱们的业务对象类可以使用默认函数,使用new建立对象呢?这里咱们须要引入IOC容器作法,也就是使用微软的Unity进行对象的注入及使用。
1)Unity的简单介绍
Unity是Unity是微软patterns& practices组用C#实现的轻量级,可扩展的依赖注入容器,它为方便开发者创建松散耦合的应用程序,
有如下优势:
1.简化了对象的建立,特别是针对分层对象结构和依赖关系;
2.需求的抽象,容许开发人员在运行时或配置文件中指定依赖关系,简化横切关注点的管理;
3.推迟为容器配置组件的时机,增长了灵活性;
4.服务定位能力,这使客户可以存储或缓存容器;
5.实例和类型拦截
Unity的依赖注入使用例子比较容易理解,具体代码以下所示。
static void Main( string[] args ) { //实例化一个控制器 IUnityContainer unityContainer = new UnityContainer(); //实现对象注入 unityContainer.RegisterType<IBird, Swallow>(); IBird bird = unityContainer.Resolve<IBird>(); bird.Say(); }
这个Unity的对象,咱们能够经过Nuget进行添加便可,添加后,在项目里面就有对应对应的程序集引用了。
2)引入Unity实现数据访问对象注入,完善逻辑层实现
了解了Unity的使用,咱们能够在BaseBLL对象基类类里面构建一个IOC的容器,并在这个容器初始化的时候,注册对应的数据访问层对象便可,以下所示。
/// <summary> /// 业务逻辑基类 /// </summary> /// <typeparam name="T">实体对象类型</typeparam> public abstract class BaseBLL<T>: IBaseBLL<T> where T : class { private static readonly object syncRoot = new Object(); protected IBaseDAL<T> baseDAL { get; set; } protected IUnityContainer container { get; set; } /// <summary> /// 默认构造函数。 /// 默认获取缓存的容器,若是没有则建立容器,并注册所需的接口实现。 /// </summary> public BaseBLL() { lock (syncRoot) { container = DALFactory.Instance.Container; if (container == null) { throw new ArgumentNullException("container", "container没有初始化"); } } }
好,默认在DALFactory的类里面,咱们就是在其实例化的时候,把须要的数据访问对象压进去,这样咱们就能够在具体的业务对象逻辑类里面实现调用,以下代码所示。
/// <summary> /// 城市的业务对象 /// </summary> public class CityBLL : BaseBLL<City> { protected ICityDAL dal; public CityBLL() { dal = container.Resolve<ICityDAL>(); baseDAL = dal; } public CityBLL(ICityDAL dal) : base(dal) { this.dal = dal; } }
若是咱们不关心DALFactory里面的构架细节,这个框架已经完成的对象的注入,能够正常使用了。
可是咱们仍是来看看它的实现细节,咱们经过单例模式(饿汉模式)构架IOC容器并注入相应的DAL对象了。
/// <summary> /// 实体框架的数据访问层接口的构造工厂。 /// </summary> public class DALFactory { //普通局部变量 private static Hashtable objCache = new Hashtable(); private static object syncRoot = new Object(); private static DALFactory m_Instance = null; /// <summary> /// IOC的容器,可调用来获取对应接口实例。 /// </summary> public IUnityContainer Container { get; set; } /// <summary> /// 建立或者从缓存中获取对应业务类的实例 /// </summary> public static DALFactory Instance { get { if (m_Instance == null) { lock (syncRoot) { if (m_Instance == null) { m_Instance = new DALFactory(); //初始化相关的注册接口 m_Instance.Container = new UnityContainer(); //手工加载 m_Instance.Container.RegisterType<ICityDAL, CityDAL>(); m_Instance.Container.RegisterType<IProvinceDAL, ProvinceDAL>(); } } } return m_Instance; } }
OK,经过上面的Unity,咱们实现了对象的注入及使用个,具体的窗体调用代码以下所示。
private void btnCity_Click(object sender, EventArgs e) { DateTime dt = DateTime.Now; CityBLL bll = new CityBLL(); var list = bll.GetAll(); this.dataGridView1.DataSource = list; Console.WriteLine("花费时间:{0}", DateTime.Now.Subtract(dt).TotalMilliseconds); } private void txtCityName_KeyUp(object sender, KeyEventArgs e) { DateTime dt = DateTime.Now; CityBLL bll = new CityBLL(); if(this.txtCityName.Text.Trim().Length > 0) { var list = bll.GetAll(s => s.CityName.Contains(this.txtCityName.Text)); this.dataGridView1.DataSource = list; } else { var list = bll.GetAll(); this.dataGridView1.DataSource = list; } Console.WriteLine("花费时间:{0}", DateTime.Now.Subtract(dt).TotalMilliseconds); }
咱们能够获得具体的界面效果以下所示。
在上面的例子里面,不知道您是否注意到了,咱们使用Unity的IOC容器的时候,注册的对象是指定的几个数据访问类。
m_Instance.Container.RegisterType<ICityDAL, CityDAL>();
m_Instance.Container.RegisterType<IProvinceDAL, ProvinceDAL>();
但这种有点相似硬编码的方式,在咱们项目若是有大量的这些数据访问类,须要手工添加的话,那真不是一件雅观的事情。
若是代码可以根据接口和接口实现类,自动把咱们所须要的接口对象注册进去,那该是多好的啊,但是能作到吗?能!
若是咱们是在同一个程序集里面执行的话,那么咱们经过反射操做,就能够从这个程序集里面获取对应的接口层(IDAL)和接口实现层(DAL)的对象,那么咱们匹配它进行对象注入就能够了吧。
下面是我动态注册DAL对象的实现代码,以下所示。
/// <summary> /// 使用Unity自动加载对应的IDAL接口的实现(DAL层) /// </summary> /// <param name="container"></param> private static void RegisterDAL(IUnityContainer container) { Dictionary<string, Type> dictInterface = new Dictionary<string, Type>(); Dictionary<string, Type> dictDAL = new Dictionary<string, Type>(); Assembly currentAssembly = Assembly.GetExecutingAssembly(); string dalSuffix = ".DAL"; string interfaceSuffix = ".IDAL"; //对比程序集里面的接口和具体的接口实现类,把它们分别放到不一样的字典集合里 foreach (Type objType in currentAssembly.GetTypes()) { string defaultNamespace = objType.Namespace; if (objType.IsInterface && defaultNamespace.EndsWith(interfaceSuffix)) { if (!dictInterface.ContainsKey(objType.FullName)) { dictInterface.Add(objType.FullName, objType); } } else if (defaultNamespace.EndsWith(dalSuffix)) { if (!dictDAL.ContainsKey(objType.FullName)) { dictDAL.Add(objType.FullName, objType); } } } //根据注册的接口和接口实现集合,使用IOC容器进行注册 foreach (string key in dictInterface.Keys) { Type interfaceType = dictInterface[key]; foreach (string dalKey in dictDAL.Keys) { Type dalType = dictDAL[dalKey]; if (interfaceType.IsAssignableFrom(dalType))//判断DAL是否实现了某接口 { container.RegisterType(interfaceType, dalType); } } } }
有了这个利用反射动态注入对象的操做,咱们在基类里面的实现就避免了硬编码的不便。
/// <summary> /// 实体框架的数据访问层接口的构造工厂。 /// </summary> public class DALFactory { //普通局部变量 private static Hashtable objCache = new Hashtable(); private static object syncRoot = new Object(); private static DALFactory m_Instance = null; /// <summary> /// IOC的容器,可调用来获取对应接口实例。 /// </summary> public IUnityContainer Container { get; set; } /// <summary> /// 建立或者从缓存中获取对应业务类的实例 /// </summary> public static DALFactory Instance { get { if (m_Instance == null) { lock (syncRoot) { if (m_Instance == null) { m_Instance = new DALFactory(); //初始化相关的注册接口 m_Instance.Container = new UnityContainer(); //根据约定规则自动注册DAL RegisterDAL(m_Instance.Container); //手工加载 //m_Instance.Container.RegisterType<ICityDAL, CityDAL>(); //m_Instance.Container.RegisterType<IProvinceDAL, ProvinceDAL>(); } } } return m_Instance; } }
上面整个框架的优化过程,都是围绕着业务逻辑层进行的,最后咱们实现了较好的动态对象的依赖注入,并给业务逻辑层对象提供了默认构造函数,让他们能够从IOC容器里面获取对象并建立。
可是咱们看到,对于EDMX文件,咱们只是把它放入了Entity的模块里面,也没有真正的对它如何处理,若是每次都须要使用这个edmx的文件生成操做,我依旧以为开发效率比较低下,并且若是对于须要支持多个数据库如何处理呢?不可能在建立一个数据操做上下文吧,它们能够已经抽象化了,自己好像不是和具体数据库相关的,和数据库相关的只是它的配置关系而已啊。
这些问题留给下一篇继续对框架的演化处理吧,谢谢你们耐心的阅读,若是以为有用,请继续推荐支持下,毕竟为了准备这个系列,我已经花了好多天的时间,从各个方面持续优化整个仓储模式的实体框架,留下一个个版本的Demo来整理博客的。
这个系列文章索引以下:
Entity Framework 实体框架的造成之旅--基于泛型的仓储模式的实体框架(1)