關於XCode數據庫反向工程的理解


  陸陸續續用Xcode組件將近一年了,作為一個業余開發者,很感謝大石頭和他的團隊。不僅感謝他們創造如此藝術的組件,更感謝他們耐心的指點,我才學會了使用模板,來開發始於自己風格和功能的通用組件。作為了老的動軟代碼生成器的使用者,但我接觸並學會使用Xcode后,以及2年來在博客園看到的各類開發框架和ORM,我不得不說Xcode是我見過最強大的,小巧,精悍。很早就想寫一篇教程,可能是基礎比較差,寫不出什么高質量的文章,畢竟是業余開發者。可能我的表述很多不專業,只有我自己理解,我也只需要對自己有用的功能。今天寫這篇博文主要是受大石頭的啟發,因為越來越多的人使用Xcode,群里面的人都爆了,但是他們又沒有太多時間來重復回答這些新手的問題,所以需要大家自力更生,看源碼去學會使用。為了貢獻自己對Xcode的理解和使用經驗,使得后來學習的人更加容易,所以才有了我的這篇文章,寫得不好,請拍磚。

1.事情起因
   NewLife.CommonEntity里面封裝了一些通用實體類,今天自己做一個小工具(個人喜好,玩玩的),想得到這些通用實體類的數據字典,雖然知道有數據庫反向工程的功能,但就是懶得動手。所以一開始手抄抄,然后在群里面哄了一下,然后群主大石頭提示了一個 CheckTables方法,其他的當然只能靠自己看源碼了。其實往往很多幫助也只需要這么關鍵的一個提示,然后自己去學習。
2.關於數據庫方向工程:就是通過配置文件切換數據庫連接字符串,不需要任何操作,系統會根據實體類自動生成數據庫及數據表。很多ORM雖然可以通過配置文件修改數據庫連接字符串,但還是需要在目標數據庫中新建好表,因此多少有些繁瑣,而Xcode徹底屏蔽了這些東西,不必要關心數據庫。因此從給一個數據庫轉化到另外一個數據庫,是再方便不過。
3.解決過程
   在石頭的提示下(CheckTables),我找到了這個方法所在的類DAL(數據訪問層),看看這個方法:
View Code
 1   ///   <summary> 檢查數據表架構,不受反向工程啟用開關限制 </summary>
 2          public  void CheckTables()
 3         {
 4             WriteLog( " 開始檢查連接[{0}/{1}]的數據庫架構…… ", ConnName, DbType);  5 
 6             Stopwatch sw =  new Stopwatch();
 7             sw.Start();
 8              try
 9             {
10                 List<IDataTable> list = EntityFactory.GetTables(ConnName);
11                  if (list !=  null && list.Count >  0)
12                 {
13                      //  全都標為已初始化的
14                      foreach (IDataTable item  in list)
15                     {
16                          if (!HasCheckTables.Contains(item.Name)) HasCheckTables.Add(item.Name);
17                     }
18 
19                      //  過濾掉被排除的表名
20                      if (NegativeExclude.Count >  0)
21                     {
22                          for ( int i = list.Count -  1; i >=  0; i--)
23                         {
24                              if (NegativeExclude.Contains(list[i].Name)) list.RemoveAt(i);
25                         }
26                     }
27                      //  過濾掉視圖
28                     list.RemoveAll(dt => dt.IsView);
29                      if (list !=  null && list.Count >  0)
30                     {
31                         WriteLog(ConnName +  " 待檢查表架構的實體個數: " + list.Count); 32 
33                         Db.CreateMetaData().SetTables(list.ToArray());
34                     }
35                 }
36             }
37              finally
38             {
39                 sw.Stop(); 40 
41                 WriteLog( " 檢查連接[{0}/{1}]的數據庫架構耗時{2} ", ConnName, DbType, sw.Elapsed);
42             }
43         }
 一開始看到這里,感覺不是我想要的,因為DAL里面需要傳入ConnName,這里有點費解,既然要反向工程,那么數據庫肯定不存在表結構,那傳入ConnName有什么用?
但是看到這但代碼,我知道了,反向工程主要是通過獲取實體類的IDataTable來完成的,就是上面代碼第33行 Db.CreateMetaData().SetTables(list.ToArray());
這里才是最關鍵的,呵呵。既然上面這個函數不滿足我的要求,那就重新寫一個滿足要求的,所以我在DAL里面給CheckTables加了一個重載方法,如下所示:
View Code
 1  public  void CheckTables(List<IDataTable> list)
 2         {
 3             WriteLog( " 開始檢查連接[{0}/{1}]的數據庫架構…… ", ConnName, DbType);
 4             Stopwatch sw =  new Stopwatch();
 5             sw.Start();
 6              try
 7             {               
 8                  if (list !=  null && list.Count >  0)
 9                 { 
10                     WriteLog(ConnName +  " 待檢查表架構的實體個數: " + list.Count);
11                     Db.CreateMetaData().SetTables(list.ToArray());                  
13                 }
14             }
15              finally
16             {
17                 sw.Stop();
18                 WriteLog( " 檢查連接[{0}/{1}]的數據庫架構耗時{2} ", ConnName, DbType, sw.Elapsed);
19             }
20         }
有了上面的這個函數,只需要把自己想要反向工程的實體類列表傳入進去就可以了,下面是我的調用方法:
View Code
1   List<IDataTable> list =  new List<IDataTable>();           
2             list.Add(Administrator.Meta.Table.DataTable);
3             list.Add(Area.Meta.Table.DataTable);
4             list.Add(Category.Meta.Table.DataTable);
5             DAL dal = DAL.Create( " Common ");
6             dal.CheckTables(list);
簡單的幾行代碼,就將 NewLife.CommonEntity中的幾個表的結構自動生成到數據庫了。我印象中記得,可以一下子獲取 NewLife.CommonEntity所有的 List<IDataTable> ,一時也想不起來,所以就只能一個個添加,呵呵,先解決實際問題,其他的再慢慢來搞。
 
4.繼續分析,雖然上面解決了的實際問題,但沒有對反向工程的整個過程有更深入的了解。而且也萌生了念頭,如此方便,那豈不是很容易的將數據庫進行遷移,表結構和數據都很容易遷移到其他數據庫平台了,正好閑着沒事,寫了一段簡單的代碼,調試看看【剛開始調試了半個小時,馬馬虎虎,有點暈,晚上回來繼續奮戰3個小時,才有點眉目,也難怪,大石頭的團隊花了好多年的成果,我怎么可能這么短時間就消化掉】。代碼很簡單,新建一個管理員,賦值並插入數據,沒有數據庫:
 
View Code
1             Administrator user =  new Administrator();
2             user.Name =  " admin ";
3             user.Password = DataHelper.Hash( " admin ");
4             user.DisplayName =  " 超級管理員 ";
5             user.RoleID =  1;
6             user.IsEnable =  true;
7             user.Insert();
斷點調試上述代碼,記下主要內容吧:
1.在new對象的過程中,會調用基類的構造函數 TEntity entity = new TEntity();然后字段賦值
2.在對象插入到數據庫中的過程中(user.Insert())會調用基類的 Insert()方法,在此方法中,有一個Valid(Boolean isNew)方法用來驗證實體的數據是否符合要求,在檢查實體數據過程中,會到數據庫中獲取一定的信息,如果數據表不存在,就會新建數據庫和相應的表結構。這一部分涉及到的類挺多的,我也有些暈。大體如下吧,可能會有理解不到位:
    2.1 為了驗證數據,需要判斷該數據是否在數據表中已經存在,所以有了Entity.cs中的CheckExist方法,其中有一個FindCount(names, values)去數據庫獲取滿足記錄的條數。
    2.2 然后獲取查詢條件,調用Entity.Meta中的QueryCount方法,然后QueryCount調用WaitForInitData方法進行 檢查並初始化數據,這時會對實體類進行檢查。
    2.3 實體類第一次檢查模型時,會調用Entity.Meta中的CheckModel()方法,這其中會對表結構的ModelCheckModes進行檢查,ModelCheckModes分為2種情況,1種是初始化時反向工程,一種是使用時反向工程。Xcode連這一點多考慮到了,說明是多么的細致,因為在我看來即使是初始化時進行,也沒有多少時間。TableItem有一個ModelCheckMode屬性,會去獲取實體類的ModelCheckMode,這個實現用到了Attribute,如果實體類聲明了ModelCheckModeAttribute,那么獲取到的就是這個值,如果沒有聲明,就是CheckAllTablesWhenInit。
    2.4 會把需要初始化化的表結構添加到List中,然后調用DBO.Db.CreateMetaData().SetTables(Table.DataTable),這就是最上面提到的那段關鍵代碼,下面進這個方法看看。
    2.5 SetTables方法中先對數據庫進行檢查( CheckDatabase()),然后檢查指定的數據表(CheckAllTables(tables))
    2.5.1 數據庫檢查時,如果數據庫不存在,會調用SetSchema(DDLSchema.CreateDatabase, null, null)創建數據庫(杯具的我,一邊調試一邊看數據庫里面有沒有生成東西,來回看,到這里才真的生成數據庫了,表還沒有)。
    2.5.2 數據表檢查時CheckAllTables(tables),這里面會去判斷對應的表是否存在數據庫,如果不存在就創建表,否則修改表結構。調用CheckTable對單個表進行檢查,創建表主要就是CreateTable方法了,運行完CreateTable,數據表Administrator也在數據庫中出來了。
 
5.改進。寫了這么多,雖然對整個過程的很多細節還不是很透徹,這篇博客開始寫的時候已經調試了3個小時左右,等寫完又基本上調試了2,3個小時,一遍調試一邊寫。寫到最后,我也對知道應該如何更好的調用方向工程方法了,我最先的調用方法也可以,但是不具有通用性。上面也提到我記得可以一次獲取所有的實體類,可能是開始太粗心,對有些過程不了解,認為EntityFactory.LoadEntities方法不能獲取到所有的實體類,但實際上應該是可以的。這其中有點小風波:
剛開始石頭提醒我用CheckTable,我就發現了EntityFactory有GetTables方法,所以我寫了這句
                                                           List<IDataTable> list = EntityFactory.GetTables("Common");
但奇怪的是這樣獲取到的List是空的,納悶了很久,最終放棄了,然后用上面的方法,先解決問題再說。等我快寫完博客,我才發現其中的原因,因為當初沒繼續往下面調試啊。因為默認是“不從未加載程序集中獲取類型”,因為我項目應用的是dll,那么應該需要加載程序集才行。所以我寫了下面的一個方法:
View Code
 1    ///   <summary>
 2           ///  獲取指定連接名下的所有實體數據表
 3           ///   </summary>
 4           ///   <param name="connName"> 數據庫連接名稱 </param>
 5           ///   <param name="isLoadAssembly"> 是否加載外部程序集 </param>
 6           ///   <returns> IDataTable集合 </returns>
 7          public  static List<IDataTable> GetTables(String connName, bool isLoadAssembly)
 8         {
 9              var tables =  new List<IDataTable>();
10              //  記錄每個表名對應的實體類
11              var dic =  new Dictionary<String, Type>(StringComparer.OrdinalIgnoreCase);
12              var list =  new List<String>();
13             IEnumerable<Type> Cur = AssemblyX.FindAllPlugins( typeof(IEntity),isLoadAssembly).Where(t => TableItem.Create(t).ConnName == connName);
14              foreach (Type item  in Cur )
15             {
16                 list.Add(item.Name);
17                  //  過濾掉第一次使用才加載的
18                  var att = ModelCheckModeAttribute.GetCustomAttribute(item);
19                  if (att !=  null && att.Mode != ModelCheckModes.CheckAllTablesWhenInit)  continue;
20                  var table = TableItem.Create(item).DataTable;
21                  // 判斷表名是否已存在
22                 Type type =  null;
23                  if (dic.TryGetValue(table.Name,  out type))
24                 {
25                      //  兩個實體類,只能要一個當前實體類是,跳過
26                      if (IsCommonEntity(item))
27                          continue;
28                      //  前面那個是,排除
29                      else  if (IsCommonEntity(type))
30                     {
31                         dic[table.Name] = item;
32                          //  刪除原始實體類
33                         tables.RemoveAll((tb) => tb.Name == table.Name);
34                     }
35                      //  兩個都不是,報錯吧!
36                      else
37                     {
38                         String msg = String.Format( " 設計錯誤!發現表{0}同時被兩個實體類({1}和{2})使用! ", table.Name, type.FullName, item.FullName);
39                         XTrace.WriteLine(msg);
40                          throw  new XCodeException(msg);
41                     }
42                 }
43                  else
44                 {
45                     dic.Add(table.Name, item);
46                 }
47 
48                 tables.Add(table);
49             }
50 
51              if (DAL.Debug) DAL.WriteLog( " [{0}]的所有實體類({1}個):{2} ", connName, list.Count, String.Join( " , ", list.ToArray()));
52 
53              return tables;
54         }
 然后在主程序中調用    List<IDataTable> list = EntityFactory.GetTables("Common");然后一句話我要的東東都有了。呵呵,其他的就不說了。
上面主要是增加了一個參數:IEnumerable<Type> Cur = AssemblyX.FindAllPlugins(typeof(IEntity),isLoadAssembly).Where(t => TableItem.Create(t).ConnName == connName);
 
呵呵,建議石頭把這個參數補充上去,因為也不費事,要是在4.0里面,來個默認參數。
下面是大石頭對反向工程的說明: 所有實體,默認情況下,根據連接名分組,只要用到這個實體,那么跟這個實體在同一個連接名旗下的所有實體,都會開始進行反向工程,建立數據庫數據表。
除了CheckAllTablesWhenInit的實體,標記CheckAllTablesWhenInit的實體,主要位於CommonEntity,它們是用到之后,只會為自己這個實體創建數據表。
因為CommonEntity里面所有實體都在Common這個連接名下,然后很多時候只需要用到CommonEntity里面的部分表,而不是全部表。
 
附上大石頭的博客和Xcode開源網址:


免責聲明!

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



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