園子里的這個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 網盤下載更快