Entity Framework 實體框架的造成之旅--利用Unity對象依賴注入優化實體框架(2)

在本系列的第一篇隨筆《Entity Framework 實體框架的造成之旅--基於泛型的倉儲模式的實體框架(1)》中介紹了Entity Framework 實體框架的一些基礎知識,以及構建了一個簡單的基於泛型的倉儲模式的框架,例子也呈現了一個實體框架應用的雛形,本篇繼續介紹這個主題,繼續深化介紹Entity Framework 實體框架的知識,以及持續優化這個倉儲模式的實體框架,主要介紹業務邏輯層的構建,以及利用Unity和反射進行動態的對象註冊。html

一、EDMX文件位置的調整

咱們從上篇例子,能夠看到這個隨筆介紹的倉儲模式的實體框架結構以下所示。數據庫

但實際上上篇隨筆的例子是有點理想化的了,由於咱們知道,【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進行對象的注入及使用。

三、使用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容器動態註冊接口對象

在上面的例子裏面,不知道您是否注意到了,咱們使用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)

Entity Framework 實體框架的造成之旅--利用Unity對象依賴注入優化實體框架(2) 

Entity Framework 實體框架的造成之旅--基類接口的統一和異步操做的實現(3)

相關文章
相關標籤/搜索