今天花了半天時間,向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項目本身規模也不大,但仍然給了我們很好的啟示:良好的架構設計能夠大幅度降低團隊資源的浪費,並且減小出錯的幾率,同時還對系統運營提供了一定的支持:我們可以讓某個團隊單獨地開發一個組件,在完成開發和測試之后,可以在不大范圍影響系統運行的基礎上將新的實現替換進去。因此,良好的架構設計將會對項目的管理帶來重大影響。