zoukankan      html  css  js  c++  java
  • 关于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开源网址:
  • 相关阅读:
    题解 P3842 【[TJOI2007]线段】
    题解 CF1366A 【Shovels and Swords】
    题解 CF1391D
    题解 CF1374B 【Multiply by 2, divide by 6】
    CSP-J2020爆零记
    YbtOJ20025 放置石子
    YbtOJ20001 立方数差
    [仅供参考]W-RB的码风及要求
    [敲黑板]CSP考试策略
    [水沝淼㵘]向量水解
  • 原文地址:https://www.cnblogs.com/asxinyu/p/2467055.html
Copyright © 2011-2022 走看看