今天花了半天時間,向Byteart Retail案例加入了基於MongoDB的倉儲實現,讀者朋友可以直接從Byteart Retail的程式碼程式庫複製最新代碼來使用基於MongoDB的倉儲實現。
實現步驟1、重構ByteartRetail.Domain.Repositories目錄結構
本來這一步是不需要做的,但是因為之前沒有把結構規劃好,所以所有基於Entity Framework的倉儲實現都放在了根目錄下。現在把這些倉儲的實現都移到了EntityFramework目錄中,同時修改了命名空間和ByteartRetail.Services項目的web.config檔案。改完後結構如下:
2、實現基於MongoDB的倉儲和上下文
在《深度剖析Byteart Retail案例:倉儲(Repository)及其上下文(Repository Context)》一文中,我已經詳細介紹了Byteart Retail案例中倉儲及其內容相關的設計和實現,因此,在已有的架構上再實現一個MongoDB的倉儲是非常容易的事情,具體實現方式在此也不多做說明了,可以結合這篇文章並參考原始碼,整個過程只花了我不到一個小時的時間。實現後的目錄結構如下:
3、將SQL LocalDB中的資料移轉到MongoDB中
這部分花了我一些時間,為了簡單起見,我還是自己寫了一些控制台代碼,基本思路是:先用EntityFrameworkRepository將對象讀入,然後以彙總根為單位,使用MongoDBRepository依次寫入MongoDB。寫入的時候遇到了一些小問題,其中最需要注意的就是,在我們的SalesLine對象中彙總了SalesOrder,而SalesOrder本身又彙總了SalesLine,這就造成了循環參考,因此MongoDB會報錯的(EF不會報錯,因為EF採用了消極式載入功能來擷取SalesLine資訊),為瞭解決這個問題,需要配置MongoDB的Class Map,如下:
BsonClassMap.RegisterClassMap<SalesLine>(s =>{ s.AutoMap(); s.SetIgnoreExtraElements(true); s.UnmapProperty<SalesOrder>(p => p.SalesOrder); // bypass circular reference.});
此外,我們需要用彙總根的ID作為MongoDB的objectID,並希望日期時間以本地時間格式儲存,因此,我另外開發了兩個Convention Profile,並在MongoDBRepositoryContext中增加了兩個靜態方法,以便應用程式在啟動的時候能夠調用這個靜態方法完成相關設定:
public class UseLocalDateTimeConvention : IMemberMapConvention{ public void Apply(BsonMemberMap memberMap) { IBsonSerializationOptions options = null; switch (memberMap.MemberInfo.MemberType) { case MemberTypes.Property: PropertyInfo propertyInfo = (PropertyInfo)memberMap.MemberInfo; if (propertyInfo.PropertyType == typeof(DateTime) || propertyInfo.PropertyType == typeof(DateTime?)) options = new DateTimeSerializationOptions(DateTimeKind.Local); break; case MemberTypes.Field: FieldInfo fieldInfo = (FieldInfo)memberMap.MemberInfo; if (fieldInfo.FieldType == typeof(DateTime) || fieldInfo.FieldType == typeof(DateTime?)) options = new DateTimeSerializationOptions(DateTimeKind.Local); break; default: break; } memberMap.SetSerializationOptions(options); } public string Name { get { return this.GetType().Name; } }}public class GuidIDGeneratorConvention : IPostProcessingConvention{ public void PostProcess(BsonClassMap classMap) { if (typeof(IEntity).IsAssignableFrom(classMap.ClassType) && classMap.IdMemberMap != null) { classMap.IdMemberMap.SetIdGenerator(new GuidGenerator()); } } public string Name { get { return this.GetType().Name; } }}public static void RegisterConventions(bool autoGenerateID = true, bool localDateTime = true){ RegisterConventions(autoGenerateID, localDateTime, null);}public static void RegisterConventions(bool autoGenerateID, bool localDateTime, IEnumerable<IConvention> additionConventions){ var conventionPack = new ConventionPack(); conventionPack.Add(new NamedIdMemberConvention("id", "Id", "ID", "iD")); if (autoGenerateID) conventionPack.Add(new GuidIDGeneratorConvention()); if (localDateTime) conventionPack.Add(new UseLocalDateTimeConvention()); if (additionConventions != null) conventionPack.AddRange(additionConventions); ConventionRegistry.Register("DefaultConvention", conventionPack, t => true);}
4、修改Global.asax.cs檔案
在Global.asax.cs檔案中,找到Application_Start方法,在這個方法中加入bootstrapper的調用:MongoDBBootstrapper.Bootstrap();。
當然,為了簡單起見,我還引入了一個MongoDBBootstrapper類,用來調用上面的RegisterConventions方法對Convention Profile進行註冊,同時還包含了對Class Map的註冊,在此就不貼代碼了。完成以上四步以後,準備工作就做好了。
啟用MongoDB倉儲及其上下文
準備工作做好以後,就讓我們開始啟用MongoDB倉儲吧。由於我上面已經完成了資料移轉,因此,在我的MongoDB資料庫中已經有了ByteartRetail資料庫。讀者朋友在完成最新版本程式碼複製品之後,請先自行安裝MongoDB服務,然後,在Byteart Retail原始碼目錄的demo_data目錄下,有個ByteartRetail.zip的壓縮包,將其解壓到你的本地磁碟,然後,進入MongoDB的bin目錄,使用mongorestore.exe程式恢複ByteartRetail資料庫。比如:
mongorestore -d ByteartRetail c:\ByteartRetail
完成資料庫恢複之後,你將可以在mongoDB的提示符下列出ByteartRetail資料庫:
下一步,修改ByteartRetail.Services下的web.config檔案,將unity配置部分中的Entity Framework倉儲及其內容相關的配置部分注釋掉,然後啟用MongoDB倉儲及其內容相關的配置,如下:
OK,現在啟動網站,從前台的角度我們基本上看不到任何變化,但此時Byteart Retail已經在使用MongoDB作為資料持久化機制了。
最後說明一下:如果你安裝MongoDB的時候不是使用的預設配置(比如你改過MongoDB的連接埠等設定),那麼,你可以修改ByteartRetail.Domain.Repositories.MongoDB.MongoDBRepositoryContextSettings 類,在這個類中根據你的具體情況對MongoDB進行配置。配置的詳細說明請參考該類中的注釋。
總結
本文也算是《深度剖析Byteart Retail案例》系列文章的一個題外篇,主要目的就是為了驗證在Byteart Retail案例中,倉儲及其內容相關的可擴充性。實驗證明,目前的架構設計能夠在不改動任何已有組件的基礎上,直接新增對其它資料持久化方案的支援,我們所要做的僅僅就是繼承幾個類、實現幾個介面,然後修改一下配置資訊。整個過程花了我一個人不到半天的時間,當然,Byteart Retail項目本身規模也不大,但仍然給了我們很好的啟示:良好的架構設計能夠大幅度降低團隊資源的浪費,並且減小出錯的幾率,同時還對系統運營提供了一定的支援:我們可以讓某個團隊單獨地開發一個組件,在完成開發與測試之後,可以在不大範圍影響系統啟動並執行基礎上將新的實現替換進去。因此,良好的架構設計將會對項目的管理帶來重大影響。