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

在本系列的第一篇隨筆《Entity Framework 實體框架的形成之旅--基于泛型的倉儲模式的實體框架(1)》中介紹了Entity Framework 實體框架的一些基礎(chǔ)知識,以及構(gòu)建了一個簡單的基于泛型的倉儲模式的框架,例子也呈現(xiàn)了一個實體框架應(yīng)用的雛形,本篇繼續(xù)介紹這個主題,繼續(xù)深化介紹Entity Framework 實體框架的知識,以及持續(xù)優(yōu)化這個倉儲模式的實體框架,主要介紹業(yè)務(wù)邏輯層的構(gòu)建,以及利用Unity和反射進行動態(tài)的對象注冊。

1、EDMX文件位置的調(diào)整

我們從上篇例子,可以看到這個隨筆介紹的倉儲模式的實體框架結(jié)構(gòu)如下所示。



但實際上上篇隨筆的例子是有點理想化的了,因為我們知道,【ADO.NET實體數(shù)據(jù)模型】生成的EDMX文件實質(zhì)上自動生成了數(shù)據(jù)訪問的上下文SqlserverContext,以及幾個表的實體類,具體的效果如下所示。



我們理想化的把它放到DAL目錄,Entity目錄下,實際上是不可以的,至少是有沖突的。
那么我們應(yīng)該如何處理,才能比較合理的處理這些自動生成的內(nèi)容呢?另外我們已經(jīng)把它上升了一層到業(yè)務(wù)層,具體的BLL分層如何處理數(shù)據(jù)訪問對象的呢,通過什么方式構(gòu)建數(shù)據(jù)訪問對象?帶著這些問題,我們再來一步步分析這個框架的內(nèi)容。

為了給實體類友好的名稱,我們順便把表名的前綴移除了,如EDMX的圖形如下所示。



為了比較好的利用EDMX文件的代碼生成,我們把這個文件整體性的移動到了Entity目錄下,如下所示。

這樣相當(dāng)于把數(shù)據(jù)訪問的上下文,以及實體類的代碼全部移動到Entity命名空間里面去了,雖然可能感覺不太好,但是我們先讓它跑起來,具體的細節(jié)后面在優(yōu)化完善。

2、業(yè)務(wù)邏輯層的設(shè)計

我們再來關(guān)注下業(yè)務(wù)邏輯層(包括業(yè)務(wù)邏輯接口層),和數(shù)據(jù)訪問層類似,我們把它構(gòu)建如下所示。
1)業(yè)務(wù)邏輯接口層

/// <summary>
/// 業(yè)務(wù)邏輯層基類接口
/// </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)業(yè)務(wù)邏輯層實現(xiàn)

/// <summary>
/// 業(yè)務(wù)邏輯基類
/// </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)業(yè)務(wù)對象類的邏輯接口層

    /// <summary>
    /// 城市的業(yè)務(wù)對象接口
    /// </summary>
    public interface ICityBLL : IBaseBLL<City>
    {
    }

4)業(yè)務(wù)對象的邏輯層實現(xiàn)

/// <summary>
/// 城市的業(yè)務(wù)對象
/// </summary>
public class CityBLL : BaseBLL<City>
{
    protected ICityDAL dal;

    public CityBLL(ICityDAL dal) : base(dal)
    {
        this.dal = dal;
    }
}

上面基本上完整的闡述了業(yè)務(wù)邏輯層的實現(xiàn)了,不過我們看到一個問題,就是不管是邏輯層基類,還是具體業(yè)務(wù)對象的邏輯對象,都沒有默認構(gòu)造函數(shù),我們不能使用new進行對象的創(chuàng)建!
這是一個嚴(yán)重的問題,那么我們?nèi)绾尾拍芤?guī)避這個問題,能夠使我們的業(yè)務(wù)對象類能夠使用默認函數(shù),使用new創(chuàng)建對象呢?這里我們需要引入IOC容器做法,也就是使用微軟的Unity進行對象的注入及使用。

3、使用Unity實現(xiàn)對象的依賴注入

1)Unity的簡單介紹
Unity是Unity是微軟patterns& practices組用C#實現(xiàn)的輕量級,可擴展的依賴注入容器,它為方便開發(fā)者建立松散耦合的應(yīng)用程序,
有以下優(yōu)點:
1.簡化了對象的創(chuàng)建,特別是針對分層對象結(jié)構(gòu)和依賴關(guān)系;
   2.需求的抽象,允許開發(fā)人員在運行時或配置文件中指定依賴關(guān)系,簡化橫切關(guān)注點的管理;
   3.推遲為容器配置組件的時機,增加了靈活性;
   4.服務(wù)定位能力,這使客戶能夠存儲或緩存容器;
5.實例和類型攔截
Unity的依賴注入使用例子比較容易理解,具體代碼如下所示。

 static void Main( string[] args )
 {
    //實例化一個控制器
    IUnityContainer unityContainer = new UnityContainer();
    
    //實現(xiàn)對象注入
    unityContainer.RegisterType<IBird, Swallow>();
    IBird bird = unityContainer.Resolve<IBird>();

    bird.Say();
}

這個Unity的對象,我們可以通過Nuget進行添加即可,添加后,在項目里面就有對應(yīng)對應(yīng)的程序集引用了。


2)引入Unity實現(xiàn)數(shù)據(jù)訪問對象注入,完善邏輯層實現(xiàn)
了解了Unity的使用,我們可以在BaseBLL對象基類類里面構(gòu)建一個IOC的容器,并在這個容器初始化的時候,注冊對應(yīng)的數(shù)據(jù)訪問層對象即可,如下所示。

/// <summary>
/// 業(yè)務(wù)邏輯基類
/// </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>
    /// 默認構(gòu)造函數(shù)。
    /// 默認獲取緩存的容器,如果沒有則創(chuàng)建容器,并注冊所需的接口實現(xiàn)。
    /// </summary>
    public BaseBLL() 
    {
        lock (syncRoot)
        {
            container = DALFactory.Instance.Container;
            if (container == null)
            {
                throw new ArgumentNullException("container", "container沒有初始化");
            }
        }
    }

好,默認在DALFactory的類里面,我們就是在其實例化的時候,把需要的數(shù)據(jù)訪問對象壓進去,這樣我們就可以在具體的業(yè)務(wù)對象邏輯類里面實現(xiàn)調(diào)用,如下代碼所示。

/// <summary>
/// 城市的業(yè)務(wù)對象
/// </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;
    }
}

如果我們不關(guān)心DALFactory里面的構(gòu)架細節(jié),這個框架已經(jīng)完成的對象的注入,可以正常使用了。

但是我們還是來看看它的實現(xiàn)細節(jié),我們通過單例模式(餓漢模式)構(gòu)架IOC容器并注入相應(yīng)的DAL對象了。

/// <summary>
/// 實體框架的數(shù)據(jù)訪問層接口的構(gòu)造工廠。
/// </summary>
public class DALFactory
{
    //普通局部變量
    private static Hashtable objCache = new Hashtable();
    private static object syncRoot = new Object();
    private static DALFactory m_Instance = null;

    /// <summary>
    /// IOC的容器,可調(diào)用來獲取對應(yīng)接口實例。
    /// </summary>
    public IUnityContainer Container { get; set; }

    /// <summary>
    /// 創(chuàng)建或者從緩存中獲取對應(yīng)業(yè)務(wù)類的實例
    /// </summary>
    public static DALFactory Instance
    {
        get
        {
            if (m_Instance == null)
            {
                lock (syncRoot)
                {
                    if (m_Instance == null)
                    {
                        m_Instance = new DALFactory();
                        //初始化相關(guān)的注冊接口
                        m_Instance.Container = new UnityContainer();

                        //手工加載
                        m_Instance.Container.RegisterType<ICityDAL, CityDAL>();
                        m_Instance.Container.RegisterType<IProvinceDAL, ProvinceDAL>();
                    }
                }
            }
            return m_Instance;
        }
    }

OK,通過上面的Unity,我們實現(xiàn)了對象的注入及使用個,具體的窗體調(diào)用代碼如下所示。
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容器動態(tài)注冊接口對象

在上面的例子里面,不知道您是否注意到了,我們使用Unity的IOC容器的時候,注冊的對象是指定的幾個數(shù)據(jù)訪問類。

 m_Instance.Container.RegisterType<ICityDAL, CityDAL>();
 m_Instance.Container.RegisterType<IProvinceDAL, ProvinceDAL>();

但這種有點類似硬編碼的方式,在我們項目如果有大量的這些數(shù)據(jù)訪問類,需要手工添加的話,那真不是一件雅觀的事情。

如果代碼能夠根據(jù)接口和接口實現(xiàn)類,自動把我們所需要的接口對象注冊進去,那該是多好的啊,可是能做到嗎?能!

如果我們是在同一個程序集里面執(zhí)行的話,那么我們通過反射操作,就可以從這個程序集里面獲取對應(yīng)的接口層(IDAL)和接口實現(xiàn)層(DAL)的對象,那么我們匹配它進行對象注入就可以了吧。

下面是我動態(tài)注冊DAL對象的實現(xiàn)代碼,如下所示。

/// <summary>
/// 使用Unity自動加載對應(yīng)的IDAL接口的實現(xiàn)(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";

    //對比程序集里面的接口和具體的接口實現(xiàn)類,把它們分別放到不同的字典集合里
    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);
            }
        }
    }

    //根據(jù)注冊的接口和接口實現(xiàn)集合,使用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是否實現(xiàn)了某接口
            {
                container.RegisterType(interfaceType, dalType);
            }
        }
    }
}

有了這個利用反射動態(tài)注入對象的操作,我們在基類里面的實現(xiàn)就避免了硬編碼的不便。

/// <summary>
/// 實體框架的數(shù)據(jù)訪問層接口的構(gòu)造工廠。
/// </summary>
public class DALFactory
{
    //普通局部變量
    private static Hashtable objCache = new Hashtable();
    private static object syncRoot = new Object();
    private static DALFactory m_Instance = null;

    /// <summary>
    /// IOC的容器,可調(diào)用來獲取對應(yīng)接口實例。
    /// </summary>
    public IUnityContainer Container { get; set; }

    /// <summary>
    /// 創(chuàng)建或者從緩存中獲取對應(yīng)業(yè)務(wù)類的實例
    /// </summary>
    public static DALFactory Instance
    {
        get
        {
            if (m_Instance == null)
            {
                lock (syncRoot)
                {
                    if (m_Instance == null)
                    {
                        m_Instance = new DALFactory();
                        //初始化相關(guān)的注冊接口
                        m_Instance.Container = new UnityContainer();

                        //根據(jù)約定規(guī)則自動注冊DAL
                        RegisterDAL(m_Instance.Container);

                        //手工加載
                        //m_Instance.Container.RegisterType<ICityDAL, CityDAL>();
                        //m_Instance.Container.RegisterType<IProvinceDAL, ProvinceDAL>();
                    }
                }
            }
            return m_Instance;
        }
    }

上面整個框架的優(yōu)化過程,都是圍繞著業(yè)務(wù)邏輯層進行的,最后我們實現(xiàn)了較好的動態(tài)對象的依賴注入,并給業(yè)務(wù)邏輯層對象提供了默認構(gòu)造函數(shù),讓他們可以從IOC容器里面獲取對象并創(chuàng)建。

但是我們看到,對于EDMX文件,我們只是把它放入了Entity的模塊里面,也沒有真正的對它如何處理,如果每次都需要使用這個edmx的文件生成操作,我依舊覺得開發(fā)效率比較低下,而且如果對于需要支持多個數(shù)據(jù)庫如何處理呢?不可能在創(chuàng)建一個數(shù)據(jù)操作上下文吧,它們可以已經(jīng)抽象化了,本身好像不是和具體數(shù)據(jù)庫相關(guān)的,和數(shù)據(jù)庫相關(guān)的只是它的配置關(guān)系而已啊。

這些問題留給下一篇繼續(xù)對框架的演化處理吧,謝謝大家耐心的閱讀,如果覺得有用,請繼續(xù)推薦支持下,畢竟為了準(zhǔn)備這個系列,我已經(jīng)花了好多天的時間,從各個方面持續(xù)優(yōu)化整個倉儲模式的實體框架,留下一個個版本的Demo來整理博客的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容