企業級應用架構(三)三層架構之數據訪問層的改進以及測試DOM的發佈

     在上一篇咱們在宏觀概要上對DAL層進行了封裝與抽象。咱們的目的主要有兩個:第一,解除BLL層對DAL層的依賴,這一點咱們經過定義接口作到了;第二,使咱們的DAL層可以支持一切數據訪問技術,如Ado.net,EF,linq To Sql,這一點咱們實現的不是很完美,仍有很大的改進空間,本文將加以改進。html

    在此以前咱們來看一下咱們最新的dom(PS:通過兩天的趕工,咱們的dom已經相對成熟,其中BLL層已經被我高度抽象化了,而且引進了業務上文文的概念;DAL層除了具體的技術實現尚爲完成,其餘方面已經相對完善了)數據庫

     DAL層的AdoDal項目和EFDAL項目,分別表明採用ado.net技術和EF技術實現數據訪問層,我在這兩個項目中分別定義了一個OrderDAL測試類session

和一個RolesDal測試類,代碼以下框架

using IDAL;
using Model;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AdoDal
{
    public class OderDAL : DALBase<Order>, IOrderDAL
    {
        public OderDAL(IDbConnection dbConnection)
        {

        }
        public override string TestMethod()
        {
            return "如今測試的是Ado的Oder類";
        }
    }
}
View Code
using IDAL;
using Model;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AdoDal
{
    public class RolesDal : DALBase<Roles>, IRoles
    {
        public RolesDal(IDbConnection dbConnection)
        {

        }
        public override string TestMethod()
        {
            return "如今測試的是Ado的roles類";
        }
    }
}
View Code
using EFDal;
using IDAL;
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFDal
{
    public class OderDAL : DALBase<Order>, IOrderDAL
    {
        public override string TestMethod()
        {
            return "如今測試的是EF的Oder類";
        }
    }
}
View Code
using IDAL;
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EFDal
{
    public class RolesDal : DALBase<Roles>, IRoles
    {

        public override string TestMethod()
        {
            return "如今測試的是EF的roles類";
        }
    }
}
View Code

   咱們在BLL項目中的OrderBLL類來調用這RolesDal與OrderDAL的測試方法TestMethod()。注:咱們的BLL層並無引用DAL層,咱們獲得的DAL層實例,是經過工廠運用反射來實現的,至於,反射獲得的OrderDAL,RolesDal是來自AdoDal項目仍是EFDAL項目,徹底是由咱們的配置文件決定的,調用代碼以下dom

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Model;
using IDAL;
using Common;
using CommonFactory;
using IBLL;

namespace BLL
{
    public class OrderBLL : BLLBase<OrderBusiness>,IOrderBLL
    {
        public OrderBLL()
        {
            BusinessContext = InstancesFactory.CreateInstances<IBusinessContext>
                (FactoryConfig.BusinessContext.AssemblyPath, FactoryConfig.BusinessContext.ClassName);
        }

        #region IOrderDAL的專用方法
        public string Test()
        {
            string str1= BusinessContext.DALSession.RolesDal.TestMethod();
            string str2= BusinessContext.DALSession.OrderDal.TestMethod();
            string str = string.Format("角色測試:{0} +  訂單測試:{1}",str1,str2);
            return str;
        }
        #endregion
    }
}
View Code

  咱們的UI層項目StructUI也實現了與BLL層的解耦,它並無引用BLL,它獲得的BLL層實例一樣是採用工廠根據配置文件經過反射來實現的。以下ide

using Common;
using CommonFactory;
using IBLL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace StructUI
{
    public partial class Test : System.Web.UI.Page
    {
        protected IBussinessSession session;
        IBussinessSessionFactory factory;
        protected void Page_Load(object sender, EventArgs e)
        {
            factory = InstancesFactory.CreateInstances<IBussinessSessionFactory>
                (FactoryConfig.BussinessSessionFactory.AssemblyPath, FactoryConfig.BussinessSessionFactory.ClassName);
            session = factory.GetSession();
            string str= session.OrderBussiness.Test();
            txtInfo.InnerText = str;
        } 
    }
}
View Code

   從上面咱們知道,UI層的頁面是經過工廠建立OrderBussiness實體,而後調用Test()方法,在把結果展現在前臺的文本域中。好了,如今咱們來開始測試。首先,咱們經過配置文件來設置對EFDAL項目中的OrderDAL和RolesDal實體進行測試,咱們配置文件以下函數

  結果如圖:
 接着咱們改變咱們的配置文件,代碼以下 測試

 結果以下:
優化

   綜上,咱們的框架實現了對數據訪問層各種技術的支持,同時咱們的成功的解除了框架中層與層的依賴(UI依賴BLL,BLL依賴DAL)。spa

  下面咱們來看一看目前版本的數據訪問層相對於上一篇的數據訪問層的改進,對照咱們上一篇的項目結構(下圖),咱們發如今的數據訪問層的DAL項目被幹掉了,AdoDal,EFDal與DALFactory這三個新項目被添加進來了。

  

    先說一說,我幹掉DAL,同時又添加AdoDal,EFDal的緣由。在上一篇文章,咱們把對不一樣數據訪問技術的實現寄託在ADOBase<T>類型與EFBase<T>類型上面,這兩個類型最終將賦值給數據訪問層基類的dalActive屬性,來幫助基類實現數據層接口,至因而哪個,則由配置文件說了算,咱們採用工廠讀取配置文件來建立這兩個類型的實例,可是這裏會碰到一個技術難題:這兩個類型都是範型,咱們沒法事先知道該類型的範型參數在實例化時會是一個什麼樣的類型,因此咱們沒辦法經過反射來動態讀取程序集和類名建立對應的範型實例,所以,在上一篇我採用了一個很是簡陋的工廠方來建立,以下

using IDAL;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;

namespace DAL
{
    public class DalActiveProvider<T> where T:class,new()
    {
        private static string className = ConfigurationManager.AppSettings["dalActiveName"];
        public static IDALBase<T> GetDalActive()
        {
            if (string.Equals(className, "EFBase"))
            {
                return new EFBase<T>();
            }
            else
            {
                return new ADOBase<T>();
            }
        }
    }
}
View Code

    這樣咱們就經過條件判斷語句寫死了程序數據訪問層所能使用的技術,在這個工廠裏面除了ado.net和EF,它將不會去建立其餘任何技術的訪問實體。咱們想要增長一種新的技術則必須從新修改工廠的代碼,這樣就違背了軟件工程的一個原則:一個好的框架,應該是在須要什麼功能的時候去擴展,而不該該是去修改之前的代碼。

   另外一個促使我改變程序框架的緣由是由於咱們目前的這種業務背景和抽象工廠模式至關的吻合。咱們數據訪問層採用什麼技術,業務邏輯層根本就不關心,咱們徹底能夠定義兩個工廠來建立兩種不一樣技術的實例,而後根據配置文件來決定採用哪個工廠。可是這裏咱們必須設想一種狀況,那就是咱們的數據訪問層的實體至關的多,若是咱們每個實體都用工廠來建立的話,那麼配置文件確定會很大,配置文件的節點一多起來,第一個不便於維護,第二個,不便於理解。所以,咱們必須找到替代的方法,我在數據訪問層定義了2個倉庫類型:ADOSession與EFSession。其中ADOSession用於獲取AdoDal項目中的全部數據訪問實體,EFSession用於獲取EFDal項目中的全部實體,代碼以下

using IDAL; using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AdoDal { public class ADOSession : ISession { private IDbConnection dbConnection; public ADOSession(IDbConnection con) { dbConnection = con; } public IOrderDAL OrderDal { get { return new OderDAL(dbConnection); } } public IUsers UserDal { get { return new UsersDal(dbConnection); } } public IRoles RolesDal { get { return new RolesDal(dbConnection); } } } }
View Code
using EFDal; using IDAL; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EFAdo { public class EFSession : ISession { public IOrderDAL OrderDal { get { return new OderDAL(); } } public IUsers UserDal { get { return new UsersDal(); } } public IRoles RolesDal { get { return new RolesDal(); } } } }
View Code

   ADOSession類型中在構造函數中須要傳入了一個IDbConnection數據庫鏈接實體,很顯然這個IDbConnection會來自BLL層,這是由於咱們的業務層須要有定製事務的能力,所以它必須可以獲得IDbConnection來發起事務,當一個事務被髮起時,全部在事務期間被建立的數據訪問實體對數據庫的操做必須是基於事務發起這的IDbConnection,這樣的操做,才受事務的控制。所以這就要求咱們的BLL層在建立DAL數據實體,有定義該實體的IDbConnection的能力,很顯然,在構造函數中傳入統一的IDbConnection是一個不錯的選擇。

   好了ADOSession,EFSession咱們都有了,如今咱們假設咱們若是可以在BLL層拿到這樣的實體,那麼咱們是否是就可以得到AdoDal或EFDal的全部實體呢?答案是顯然的,可是這樣問題又來了,咱們BLL並無引用DAL,因此這兩個倉庫實體什麼類型BLL確定是不知道的,BLL只認接口,所以咱們必須爲倉庫定義接口,以下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IDAL
{
    public interface ISession
    {
        IOrderDAL OrderDal
        {
            get;
        }
        IUsers UserDal
        {
            get;
        }
        IRoles RolesDal 
        { 
            get;
        }
    }
}
View Code

   另外,咱們還必須有相應的實例化機制,給BLL層的調用者提供實例化服務。所以咱們想到提供兩套數據訪問層實例工廠來爲調用者提供實例化,至於到底選擇哪一套工廠,則徹底由配置文件說了算。咱們的兩個工廠都實現了工廠接口,代碼以下

using AdoDal;
using IDAL;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;

namespace DALFactory
{
    public class ADOSessionFactory:ISessionFactory
    {
        private IDbConnection dbConnection;
        public ADOSessionFactory(IDbConnection con)
        {
            dbConnection = con;
        }
        public ISession GetSession()
        {
            return new ADOSession(dbConnection);
        }
    }
}
View Code
using EFAdo;
using IDAL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DALFactory
{
    public class EFSessionFactory : ISessionFactory
    {
        public ISession GetSession()
        {
            return new EFSession();
        }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IDAL
{
    public interface ISessionFactory
    {
        ISession GetSession();
    }
}
View Code

   在BLL層的業務上下文中,咱們把對應的ISessionFactory在構造函數中經過工廠讀取配置文件進行實例化,代碼以下

using Common;
using CommonFactory;
using IBLL;
using IDAL;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;

namespace BLL
{
    public class AdoBussinessContext : IBusinessContext
    {
        /// <summary>
        /// 連接字符串
        /// </summary>
        protected IDbConnection Con{ get; private set; }
        protected ISessionFactory sessionFactory { get; private set; }
     
        public AdoBussinessContext()
        {
            ConnectionFactory factory = new ConnectionFactory("str");
            Con = factory.CreateConnection();
            sessionFactory = InstancesFactory.CreateInstances<ISessionFactory>(FactoryConfig.SessionFactory.AssemblyPath,
                FactoryConfig.SessionFactory.ClassName, new object[]{ Con });
        }
        public ISession DALSession
        {
            get
            {
               return sessionFactory.GetSession();
            }
        }

        public IDbTransaction BeginTransaction(IsolationLevel level)
        {
           return Con.BeginTransaction(level);
                     
        }
           
        public IDbTransaction BeginTransaction()
        {
           return Con.BeginTransaction();
        }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace CommonFactory
{
    public class InstancesFactory
    {
        /// <summary>
        /// 建立指定類型T的實例
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="assemblyPath">程序集路徑</param>
        /// <param name="className">類名稱(非全名)</param>
        /// <returns>T類型實例</returns>
        public static T CreateInstances<T>(string assemblyPath, string className, object[] args = null)
        {
            className = string.Format("{0}.{1}", assemblyPath, className);
            Assembly assembly = Assembly.Load(assemblyPath);
            T instances;
            if(args!=null)
            {
                instances=(T)assembly.CreateInstance(className, true, BindingFlags.Default, null, args, null, null);
            }
            else
            {
                try
                {
                    instances = (T)assembly.CreateInstance(className);
                }
                catch(Exception ex)
                {
                    throw ex;
                }
            }
            return instances;
        }
    }
}
View Code
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;

namespace Common
{
    public class ConfigEntity
    {
        /// <summary>
        /// 程序路徑
        /// </summary>
        public string AssemblyPath { get; set; }
        /// <summary>
        /// 類命
        /// </summary>
        public string ClassName { get; set; }
    }

    public class FactoryConfig
    {
        public static ConfigEntity SessionFactory
        {
            get 
            {
                return new ConfigEntity()
                {
                    AssemblyPath = ConfigurationManager.AppSettings["SessionFactoryAssemblyPath"],
                    ClassName = ConfigurationManager.AppSettings["SessionFactory"]
                };
            }
        }
        public static ConfigEntity BusinessContext
        {
            get
            {
                return new ConfigEntity()
                {
                    AssemblyPath = ConfigurationManager.AppSettings["BusinessContextAssemblyPath"],
                    ClassName = ConfigurationManager.AppSettings["BusinessContext"]
                };
            }
        }

        public static ConfigEntity BussinessSessionFactory
        {
            get
            {
                return new ConfigEntity()
                {
                    AssemblyPath = ConfigurationManager.AppSettings["BussinessSessionFactoryAssemblyPath"],
                    ClassName = ConfigurationManager.AppSettings["BussinessSessionFactory"]
                };
            }
        }
    }
}
View Code

   配置文件參見本文DOM演示部分,在BLL層的業務上下文咱們就能夠經過ISessionFactory拿到ISession實體了,有了ISession實體咱們就有了基於一種數據訪問技術的全部數據訪問層的實體了。咱們BLL層能夠大搖大擺的調用咱們的數據訪問實體操做數據庫了。至此咱們的數據訪問層的抽象已經基本完成,剩下來的就是把數據訪問層AdoDal,EFDal兩個項目中的具體技術細節所有實現,這將是我後續文章的內容呢.......

總結

    本文在上一篇文章的基礎上面繼續優化了數據訪問層,使咱們的數據訪問層更加的完善與成熟。一個好的應用框架老是運用中被不斷的完善,咱們在開發的時候,多想想咱們所用框架的侷限性,咱們總能找到相應的優化點,最後感謝你們的觀看,本文DOM的源碼請點擊  這裏

相關文章
相關標籤/搜索