基於EF4.1的異構資料庫訪問組件(一)

來源:互聯網
上載者:User
開篇

     經過3年的努力與付出,終於實現了我的職業規劃——系統架構師。先就職於某女性內衣電商ERP團隊,由於一些原因,轉至某捕魚遊戲公司,找份想要的工作真的不容易。呵呵,感慨一下。。

      特此記錄一下作為架構師的第一個作品,與大家分享交流。。

      感謝Google,感謝Codeplex,感謝廣州知微團隊中曾經協助我的同事們

需求背景

   在現有很多系統中,常常有多個資料庫,多種資料庫的使用。因此在程式中,我們需要一個這樣的資料訪問組件:

    1. 支援多個資料庫;
    2. 支援多種資料庫:SQL SERVER / MYSQL / ORACLE;
    3. 提供統一持久介面;
    4. 配置及初始化靈活;
    5. 業務對象與資料庫物件之間的映射配置方便;
為啥是EF4.1

      拿到這些需求,我的第一反應是利用現有的ORM:NH或EF進行封裝改造,自己造輪子雖然能學到不少東西,但可行性與風險相對較高。

      經過一些思考與測試,我選擇了EF4.1作為封裝改造的目標,有以下幾點原因:

  • 微軟系愛微軟 :)
  • EF4.1支援POCO,映射配置類,還有超強工具EF Power Tool
  • 對比了EF4.1與NH3,兩者的功能差不多,基於現有團隊的情況,EF4.1相對學習成本較低;
  • LINQ TO Entities 相對 LINQ TO NH來說,實現得更加全面;
多個資料庫IDbContextStorage

       確定使用EF4.1進行封裝後,碰到了第一個問題:怎樣支援多個資料庫呢?

       我們使用EF4.1時一般使用以下代碼:

public class TestDbContext :DbContext{     public DbSet<UserInfo> UserInfoes {get; set;}          public DbSet<UserAddress> UserAddresses {get;set;}     ...}
using(var ctx = new TestDbContext()){    // doSomething} 

       這下我就鬱悶了,如果要支援多個資料庫,可能是TestDb1Context,TestDb2Context的類寫在項目中,這樣就有以下幾點壞處了:

  • 無法提供統一的資料提供者;
  • 有幾個資料庫就會有幾個DbContext,而且直接將DbContext暴露給了上層;
  • 管理麻煩;


      這時突然想起來在知微時的一個開源項目SharpArch,看了看它裡面NH的一些寫法,好眼熟呢,這不正是我想要的麼。多個資料庫的話,我也可以通過一個IDbContextStorage來進行管理。 代碼如下:

/// <summary>/// DbContext倉庫介面
/// </summary>public interface IDbContextStorage{    /// <summary>    /// 根據key擷取DbContext    /// </summary>    /// <param name="key">key</param>    /// <returns>DbContext</returns>    DbContext GetByKey(string key);    /// <summary>    /// 根據Key將DbContext寫入Storage    /// </summary>    /// <param name="key">Key</param>    /// <param name="dbContext">DbContext</param>    void SetByKey(string key, DbContext dbContext);    /// <summary>    /// 擷取所有DDbContext    /// </summary>    /// <returns>所有DDbContext</returns>    IEnumerable<DbContext> GetAllDbContexts();}

     

      接下來,我們可以實現IDbContextStorage,用於WEB應用程式的WebDbContextStorage:

/// <summary>/// 用於WEB應用程式的DbContext/// </summary>public class WebDbContextStorage :IDbContextStorage{    // DbContext容器    private Dictionary<string, DbContext> _storage = new Dictionary<string, DbContext>();    /// <summary>    /// 建構函式    /// 用於註冊HttpApplication.EndRequest事件,關閉資料庫連接    /// </summary>    /// <param name="app"></param>    public WebDbContextStorage(HttpApplication app)    {        app.EndRequest += (sender, args) =>                              {                                   DbContextManager.CloseAllDbContexts();
                              };    }        #region Implementation of IDbContextStorage    /// <summary>    /// 根據key擷取DbContext    /// </summary>    /// <param name="key">key</param>    /// <returns>DbContext</returns>    public DbContext GetByKey(string key)    {        DbContext context;        return !_storage.TryGetValue(key, out context) ? null : context;    }    /// <summary>    /// 根據Key將DbContext寫入Storage    /// </summary>    /// <param name="key">Key</param>    /// <param name="dbContext">DbContext</param>    public void SetByKey(string key, DbContext dbContext)    {        _storage.Add(key, dbContext);    }    /// <summary>    /// 擷取所有DDbContext    /// </summary>    /// <returns>所有DDbContext</returns>    public IEnumerable<DbContext> GetAllDbContexts()    {        return _storage.Values;    }    #endregion}

    此處存在的疑問點:由於EF自己進行資料庫連接的管理,由於HttpApplication.EndRequest事件中關閉資料庫連接此種方法效果如何還沒有進行過測試,不過不礙事。

動態組建DbContext:IDbContextBuilder

     到了這一步,我得怎樣去除項目代碼中的多個DbContext,即動態地去組建DbContext呢。。

    OK,Google,Codeplex,Google code輪番上陣,查呀查呀。目的只有一個,弄清楚DbContext是怎樣組建的,終於在某段代碼中發現了,原來有DbModelBuilder這個東東,爽呐,動手開搞

    其原理是,DbContext分為2個步驟組建:

  • 載入映射設定檔
  • 根據連接字串及資料庫Provider準備資料庫連接
/// <summary>/// DbContext組裝介面
/// </summary>public interface IDbContextBuilder<T> where T:DbContext{    /// <summary>    /// 組裝DbContext    /// </summary>    /// <returns>DbContext</returns>    T BuildDbContext();}

  
public class DbContextBuilder<T> : DbModelBuilder, IDbContextBuilder<T> where T : DbContext{    // 資料庫用戶端ProviderFactory    private readonly DbProviderFactory _factory;    // 連接字串配置    private readonly ConnectionStringSettings _cnStringSettings;    // 如果資料庫不存在是否建立    private readonly bool _recreateDatabaseIfExists;    // 是否LazyLoad    private readonly bool _lazyLoadingEnabled;    public DbContextBuilder(string connectionStringName, string mappingAssemblyPath, string mappingNamespace, bool recreateDatabaseIfExists = false, bool lazyLoadingEnabled = true)    {        this.Conventions.Remove<IncludeMetadataConvention>();        _cnStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName];        _factory = DbProviderFactories.GetFactory(_cnStringSettings.ProviderName);        _recreateDatabaseIfExists = recreateDatabaseIfExists;        _lazyLoadingEnabled = lazyLoadingEnabled;        AddConfigurations(mappingAssemblyPath, mappingNamespace);    }    private void AddConfigurations(string mappingAssemblyPath, string mappingNamespace)    {        if (string.IsNullOrEmpty(mappingAssemblyPath))            throw new ArgumentNullException("mappingAssemblyPath");        if (string.IsNullOrEmpty(mappingNamespace))            throw new ArgumentNullException("mappingNamespace");        var hashMapping = false;
        // 關鍵代碼        var asm = Assembly.LoadFrom(GetAssemblyPath(mappingAssemblyPath));        foreach (var type in asm.GetTypes().Where(c =>            !c.IsAbstract            && c.BaseType != null            && c.BaseType.IsGenericType            && c.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>))            )        {            hashMapping = true;            dynamic configurationInstance = Activator.CreateInstance(type);            Configurations.Add(configurationInstance);        }        if(!hashMapping)            throw new ApplicationException("DbContext組裝時,沒有發現映射配置類");    }    private static string GetAssemblyPath(string assemblyName)    {        return (assemblyName.IndexOf(".dll") == -1)            ? assemblyName.Trim() + ".dll"            : assemblyName.Trim();    }    #region Implementation of IDbContextBuilder<T>    /// <summary>    /// 組é裝°DbContext    /// </summary>    /// <returns>DbContext</returns>    public T BuildDbContext()    {
        // 關鍵代碼        var cn = _factory.CreateConnection();        cn.ConnectionString = _cnStringSettings.ConnectionString;        var dbModel = this.Build(cn);        var ctx = dbModel.Compile().CreateObjectContext<ObjectContext>(cn);        ctx.ContextOptions.LazyLoadingEnabled = this._lazyLoadingEnabled;        if (!ctx.DatabaseExists())        {            ctx.CreateDatabase();        }        else if (_recreateDatabaseIfExists)        {            ctx.DeleteDatabase();            ctx.CreateDatabase();        }        return (T)new DbContext(ctx, false);    }    #endregion}

上述代碼中,最關鍵的地方是:

  • 將DbContext相關的映射配置類載入到DbModelBuilder的配置列表中,這裡我提供給了外部2個參數mappingAssemblyPath-映射配置類DLL的路徑,mappingNamespace-該DbContext的映射配置類所在的命名空間
  • 準備資料庫連接,根據DbProviderFactory,ConnectionString得到資料庫類型與資料庫連接字串,另外還有2個參數recreateDatabaseIfExists-資料庫不存在時是否自動建立,lazyLoadingEnabled-是否消極式載入。
後續

       由於時間的原因,後續的介紹只能放到後續了。。

     大致有以下幾塊:

  • DbContexts在應用程式開始時的初始化裝配器;
  • 統一持久介面IRepository<T>及其實現EFRepository<T>,這個網上有很多例子了;
  • UnitOfWork的實現:事件,批量提交,資源回收;
  • PagedList<T>的分頁機制;
  • 多條件動態組合查詢與排序;

效能最佳化

  • 代碼實戰測試及SQL效能跟蹤;
  • 跨庫關聯查詢,大表關聯查詢所帶來效能問題的解決;
  • 利用Sql Server Profiler 進行效能調優;

    希望園子裡的朋友們能提出寶貴建議,一起提高!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.