.NET程序遷移到Mysql的極簡方案——讓GGTalk同時支持Sqlserver與mysql全程記錄!


        園子里的這個GGTalk,咱們前前后后用它移花接木做的IM項目也不下三四個了。初次入手的時候,洋洋代碼,多少感覺有些難以把握。不過一來二去,理清了頭緒,也就一覽無余了。相信跟我們一樣想要利用GGTalk的同學大有人在,於是我打算寫這樣一個《GGTalk源碼詳解系列》,把自己對GGTalk的梳理分享給大家,讓大家更容易上手。

        之前有一個企業級的IM項目,我們用GGTalk改造的,但是要求使用Mysql數據庫,所以花了一番功夫將GGTalk遷移到的Mysql,功夫不負有心人,總算弄出了個成果。如今使用Mysql的企業還真不少,今天就來給大家分享分享,對大家一定會有所幫助! 

常用的數據庫:
         商業:oracle, SQLserver,DB2
         開源:MySQL,postgreSQL,SQLite 

MySQL

        是一個小型關系型數據庫管理系統,開發者為瑞典MySQL AB公司。

        在2008年1月16號被Sun公司收購。而2009年,SUN又被Oracle收購。

        由於其體積小、速度快、總體擁有成本低,尤其是開放源碼這一特點,MySQL得到了廣泛地應用。

        

Step0.Mysql安裝相關問題

          1.1045 Access denied for user 'root'@'localhost' 
          解決方案:在my.ini文件中的[mysqld]下面一行添加 skip_grant_tables

          2.1067 進程意外終止

          解決方案:my.ini中將default-storage-engine=INNODB改成MYISAM

          3.cmd中顯示mysql不是內部或外部命令

          解決方案:環境變量Path中添加mysql安裝目錄bin的全路徑        

Step1.啟動Mysql服務

          

Step2.導入sql腳本

          其中有三張表:

                 

          1.GGUser表:

          

          2.GGgroup表:

          

          3.ChatMessageRecord表:

          

          ChatMessageRecord中的索引

           

Step3.修改服務端配置文件 

    <!--數據庫相關信息配置-->
    <!--使用內存虛擬數據庫-->
    <add key="UseVirtualDB" value="true"/>
    <!--數據庫類型:SqlServer、MySQL-->
    <add key="DBType" value="MySQL"/>
    <!--數據庫名稱-->
    <add key="DBName" value="GGTalk"/>
    <!--數據庫IP-->
    <add key="DBIP" value="127.0.0.1"/>
    <!--數據庫Port,SqlServer默認為1433-->
    <add key="DBPort" value="1433"/>
    <!--數據庫sa或root的密碼-->
    <add key="SaPwd" value="123qwe"/>

          其中UseVirtualDB賦值為false表示使用真實數據庫。數據庫類型則可以配置為Sqlserver或者Mysql,程序中默認使用的是Sqlserver,需要切換到Mysql的時候修改此處即可。    

Step4.在服務端引用MySql.Data.dll

Step5.修改服務端的RealDB類的構造方法 

        /// <summary>
        /// 該構造函數用於SqlServer數據庫。
        /// </summary>        
        public RealDB(string sqlServerDBName, string dbIP,string saPwd )
        {
            DataConfiguration config = new SqlDataConfiguration(dbIP, "sa", saPwd, sqlServerDBName);
            this.transactionScopeFactory = new TransactionScopeFactory(config);
            this.transactionScopeFactory.Initialize();
            base.Initialize(this.transactionScopeFactory);
        }

        /// <summary>
        /// 該構造函數用於MySQL數據庫。
        /// </summary>   
        public RealDB(string mysqlDBName, string dbIP, int dbPort, string rootPwd)
        {
            DataConfiguration config = new MysqlDataConfiguration(dbIP, dbPort, "root", rootPwd, mysqlDBName);
            this.transactionScopeFactory = new TransactionScopeFactory(config);
            this.transactionScopeFactory.Initialize();
            base.Initialize(this.transactionScopeFactory);
        }

 Step6.重新編譯,大功告成!

———————————————————分割線—————————————————————

     該解決方案目前已經分享給到GGTalk的作者,GGTalk也更新了最新版本V5.5,大家可以去下載部署到到Mysql了!!!

     源碼下載GGTalk-V5.5.rar     網盤下載更快 

   部署下載GGTalk V5.5 可直接部署版本    網盤下載更快

原理淺析: 

        正如ORM名稱所指示的,實現ORM的關鍵點在於解決“對象--關系”之間的映射,例如,如何將一個DataRow轉換為一個Entity Object,又如何將一個對某Entity Object的操作映射到一個IDbCommand,等等。比如我們以使用IORMapping接口來抽象這些映射:

  public interface IORMapping<TEntity>
    {
        TEntity GetEntityFrom(DataRow row ,bool withBlob);
        
        /// <summary>
        /// FillParameterValue  使用entity的內容填充command中的各個IDbDataParameter的參數值。
        /// </summary>      
        void FillParameterValue(IDbCommand command, TEntity entity);
     }

       關於如何實現IORMapping接口,至少有四種方案。我們以實現GetEntityFrom方法為例為例來講述這四種方案,這個方法是將一個DataRow轉換為一個Entity Object。 

1.代碼生成器 

       現在有很多代碼生成器可以直接生成實現了ORM功能的DAL層,其背后的原理是,根據數據表的大綱(如有哪些列、每個列的類型等信息)來生成對應的實現了IORMapping接口的類。代碼生成器是在編譯之前就完成了這些工作的。 

2.反射 

       將一個DataRow轉換為一個Entity Object,如果粒度更細一點,我們實際要解決的是如何將DataRow的一列的值賦值給Entity Object對應的屬性,我們可以使用反射在運行時給Entity Object的屬性賦值,如:

entityType.InvokeMember(columnName, BindingFlags.Public | BindingFlags.IgnoreCase |
BindingFlags.Instance | BindingFlags.SetProperty, null, entity, row[columnName]);

     這行代碼的含義是將row中【columnName】列的值賦值給entity對象的【columnName】屬性。 

3.Emit 

       我們可以在運行時根據每個Entity類型動態發射對應的實現了IORMapping接口的類型,這些動態類型發射成功后,便可以被實例化然后被使用。比如我們要發射實現GetEntityFrom方法的代碼:

        private void EmitGetEntityFromMethod(TypeBuilder typeBuilder, MethodInfo baseMethod, Type entityType, DataSchema dataSchema)
        {
            MethodBuilder methodBuilder = typeBuilder.DefineMethod("GetEntityFrom", baseMethod.Attributes & ~MethodAttributes.Abstract, baseMethod.CallingConvention, baseMethod.ReturnType, EmitHelper.GetParametersType(baseMethod));
            ILGenerator ilGenerator = methodBuilder.GetILGenerator();
            Label retLabel = ilGenerator.DefineLabel();
            ilGenerator.DeclareLocal(entityType); //Member member = null ;
            ilGenerator.Emit(OpCodes.Nop);
            ilGenerator.Emit(OpCodes.Newobj, entityType.GetConstructor(new Type[] { }));
            ilGenerator.Emit(OpCodes.Stloc_0); //member = new Member() ;

            IList<PropertyInfo> blobList = new List<PropertyInfo>();
            #region 為非blob屬性賦值
            foreach (PropertyInfo property in entityType.GetProperties())
            {
                ColumnSchema columnSchema = dataSchema.GetColumnSchema(property.Name);
                if (columnSchema == null)
                {
                    continue;
                }

                if ((property.PropertyType == typeof(byte[])) && (!columnSchema.IsTimestamp))
                {
                    blobList.Add(property);
                    continue;
                }

                EmitSetProperty(entityType, ilGenerator, property);
            }
            #endregion

            if (blobList.Count > 0)
            {
                ilGenerator.Emit(OpCodes.Ldarg_2);
                ilGenerator.Emit(OpCodes.Brfalse, retLabel);

                #region 為blob屬性賦值
                foreach (PropertyInfo property in blobList)
                {
                    EmitSetProperty(entityType, ilGenerator, property);
                }
                #endregion
            }

            ilGenerator.MarkLabel(retLabel);
            ilGenerator.Emit(OpCodes.Nop);
            ilGenerator.Emit(OpCodes.Ldloc_0);
            ilGenerator.Emit(OpCodes.Ret);

            typeBuilder.DefineMethodOverride(methodBuilder, baseMethod);    // 定義方法重載
        }

4.使用Lamda表達式 

       如果是在.NET3.5上,我們可以使用動態生成的Lamda表達式來完成Entity Object屬性的賦值操作,關鍵點是要如何生成用於賦值的動態委托。比如: 

 private Action<TEntity, object> CreateFunctionOfSetProperty<TEntity>(MethodInfo setPropertyMethod, Type columnType)
        {
            ParameterExpression paramEntityObj = Expression.Parameter(typeof(TEntity), "entity");
            ParameterExpression paramProVal = Expression.Parameter(typeof(object), "propertyVal");
            UnaryExpression paramProVal2 = Expression.Convert(paramProVal, columnType);
            MethodCallExpression body = Expression.Call(paramEntityObj, setPropertyMethod, paramProVal2);
            Expression<Action<TEntity, object>> setPropertyExpression = Expression.Lambda<Action<TEntity, object>>(body, paramEntityObj, paramProVal);

            Action<TEntity, object> setPropertyAction = setPropertyExpression.Compile();

            return (entity, propertyVal) => { setPropertyAction(entity, propertyVal); };
        } 

     這個方法返回一個委托,返回的委托接收兩個參數--Entity Object 和要賦的屬性值,調用這個委托便可以為Entity Object的某個屬性進行賦值。 

     好了,四種方案已經簡單介紹完畢,下面我們來比較一下。 

(1)除了第一種方案是在編譯期完成外,后面三種方案都是在運行期完成的。 

(2)第一種方案的效率是最高的,但是所需的手工操作也是最多的(比如每次修改了表結構都需要重新生成DAL層)。第二種方案的效率是最低的,反射使效率的折損非常之大。后兩種方案的效率都不錯(幾乎接近第一種方案)。 

(3)Emit方案的實現難度應當是最大的,其次是Lamda表達式方案。 

(4)Lamda表達式方案在.NET3.5及以上平台才可使用。 

     源碼下載GGTalk-V5.5.rar     網盤下載更快 

   部署下載GGTalk V5.5 可直接部署版本    網盤下載更快

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM