• Entity Framework 实体框架的形成之旅--利用Unity对象依赖注入优化实体框架(2)


    在本系列的第一篇随笔《Entity Framework 实体框架的形成之旅--基于泛型的仓储模式的实体框架(1)》中介绍了Entity Framework 实体框架的一些基础知识,以及构建了一个简单的基于泛型的仓储模式的框架,例子也呈现了一个实体框架应用的雏形,本篇继续介绍这个主题,继续深化介绍Entity Framework 实体框架的知识,以及持续优化这个仓储模式的实体框架,主要介绍业务逻辑层的构建,以及利用Unity和反射进行动态的对象注册。

    1、EDMX文件位置的调整

    我们从上篇例子,可以看到这个随笔介绍的仓储模式的实体框架结构如下所示。

    但实际上上篇随笔的例子是有点理想化的了,因为我们知道,【ADO.NET实体数据模型】生成的EDMX文件实质上自动生成了数据访问的上下文SqlserverContext,以及几个表的实体类,具体的效果如下所示。

    我们理想化的把它放到DAL目录,Entity目录下,实际上是不可以的,至少是有冲突的。

    那么我们应该如何处理,才能比较合理的处理这些自动生成的内容呢?另外我们已经把它上升了一层到业务层,具体的BLL分层如何处理数据访问对象的呢,通过什么方式构建数据访问对象?带着这些问题,我们再来一步步分析这个框架的内容。

    为了给实体类友好的名称,我们顺便把表名的前缀移除了,如EDMX的图形如下所示。

    为了比较好的利用EDMX文件的代码生成,我们把这个文件整体性的移动到了Entity目录下,如下所示。

    这样相当于把数据访问的上下文,以及实体类的代码全部移动到Entity命名空间里面去了,虽然可能感觉不太好,但是我们先让它跑起来,具体的细节后面在优化完善。

    2、业务逻辑层的设计

    我们再来关注下业务逻辑层(包括业务逻辑接口层),和数据访问层类似,我们把它构建如下所示。

    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进行对象的注入及使用。

    3、使用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);
            }
    复制代码

    我们可以得到具体的界面效果如下所示。

    4、使用反射操作,在Unity容器动态注册接口对象

    在上面的例子里面,不知道您是否注意到了,我们使用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来整理博客的。

    1. 顺序
          当创建一个派生类的对象时,系统首先自动创建一个基类对象,也就是说,在调用派生类构造函数创建派生类对象之前,系统首先调用基类的构造函数创建基类对象。当派生类对象生命期结束时,首先调用派生类的析构函数,然后调用基类的析构函数。简而言之,就是说,构造函数:基类->派生类。析构函数:派生类->基类。
    这个我们完全可以通过一个小程序来说明:

    复制代码
    //通过输出就可以看出在创建派生类对象b1时各个函数的调用顺序了
    #include <iostream>
    using namespace std;
    class A
    {
    public:
        A()
        {
            cout<<"A(Based Class) constructor is called"<<endl;
        }
        ~A()
        {
            cout<<"A(Based Class) destructor is called"<<endl;
        }
    };
    class B:public A
    {
    public:
        B()
        {
            cout<<"B(Derived Class) constructor is called"<<endl;
        }
        ~B()
        {
            cout<<"B(Derived Class) destructor is called"<<endl;
        }
    };
    
    int main()
    {
        B b1;
        return 0;
    }
    复制代码

    2. 通过派生类的构造函数调用基类的构造函数有两种方式,隐式和显式两种。所谓隐式方式就是在派生类的构造函数中不指定对应的基类的构造函数,这个时候调用的是基类的默认构造函数(即含有默认参数值或不带参数的构造函数)。而所谓显式方式,就是在派生类的构造函数中指定要调用的基类的构造函数,并将派生类构造函数的部分参数值传递给基类构造函数。注:除非基类有默认的构造函数,否则必须采用显式调用方式

    下面分别给出一个隐式和显式调用的例子:

    复制代码
    #include <iostream>
    using namespace std;
    
    class A
    {
    public:
        A(int x = 0,int y = 0)
        {
            a = x;
            b = y;
        }
    private:
        int a;
        int b;
    };
    //基类A有默认的构造函数,可以隐式调用
    class B:public A
    {
    public:
        B(int z = 0)
        {
            c = z;
        }
    private:
        int c;
    };
    
    int main()
    {
        B b1;
        return 0;
    }
    复制代码
    按 Ctrl+C 复制代码
  • 相关阅读:
    2020年勒索软件攻击最多的四大漏洞
    物联网时代,我们还能有哪些隐私?
    区块链技术最重要价值所在
    为什么云遣返不仅仅是从公共云回到内部部署环境
    如何应对越来越多的物联网勒索软件威胁?
    如何将边缘计算与核心系统集成
    莫唱衰:5G开局很快,但原力呈现才刚刚开始
    云原生架构支撑千万级DAU游戏
    变革型AI、无代码与低代码——哪一种才是企业AI部署的理想途径?
    nginx代理的配置和文件访问权限配置
  • 原文地址:https://www.cnblogs.com/sjqq/p/7702016.html
Copyright © 2020-2023  润新知