CodeSmith 使用教程(15) 为Yii Framework 创建生成ActiveRecord的代码模板

jerry CodeSmith 2015年11月25日 收藏

CodeSmith 使用教程(3): 自动生成Yii Framework ActiveRecord 我们通过SchemaExploer为Yii Framework从数据库生成简单的ActiveRecord类,没有考虑到表和表之间的关系。我们使用CodeSmith为Yii Framework创建一个通用的代码模板,可以使用上例介绍的SchemaExploer ,不过在查看CodeSmith自带的例子中有个生成Hibernate的例子,这个模板的使用可以参见CodeSmith 使用教程(1): 概述 ,CodeSmith提供了这个模板的源码,使用到了CodeSmith.SchemaHelper (CodeSmith没有提供相应的文档),不过可以通过阅读NHiberante的模板了解其一般用法。

为生成Yii Framework ActiveRecord类之间的relation ,先要了解一下表和表之间的关系:

两个 AR 类之间的关系直接通过 AR 类所代表的数据表之间的关系相关联。 从数据库的角度来说,表 A 和 B 之间有三种关系:一对多(one-to-many,例如 tbl_user 和 tbl_post),一对一( one-to-one 例如 tbl_user 和tbl_profile)和 多对多(many-to-many 例如 tbl_category 和 tbl_post)。 在 AR 中,有四种关系:

  • BELONGS_TO(属于): 如果表 A 和 B 之间的关系是一对多,则 表 B 属于 表 A (例如 Post 属于 User);
  • HAS_MANY(有多个): 如果表 A 和 B 之间的关系是一对多,则 A 有多个 B (例如 User 有多个 Post);
  • HAS_ONE(有一个): 这是 HAS_MANY 的一个特例,A 最多有一个 B (例如 User 最多有一个 Profile);
  • MANY_MANY: 这个对应于数据库中的 多对多 关系。 由于多数 DBMS 不直接支持 多对多 关系,因此需要有一个关联表将 多对多 关系分割为 一对多 关系。 在我们的示例数据结构中,tbl_post_category 就是用于此目的的。在 AR 术语中,我们可以解释 MANY_MANY 为 BELONGS_TO 和 HAS_MANY 的组合。 例如,Post 属于多个(belongs to many) Category ,Category 有多个(has many) Post.

还是使用Chinook数据库,修改Yii Framework 开发教程(27) 数据库-关联Active Record示例。数据表之间的关系如下:

20130107001

CodeSmith 中PLINQO-NH代码位置:

缺省目录为C:\Program Files (x86)\CodeSmith\v6.5\Samples\Templates\Frameworks\PLINQO-NH

20130111001

CodeSmith.SchemaHelper定义的主要类有:

20130111002

几个主要的类为

  • EntityManager 管理所有的Entity(对应于整个数据库)
  • Entity实体类(对应到单个表,视图)
  • IAssoication 关系(定义表和表之间的关系)
  • AssoicationType 关系的类型 (见下表)

根据AssociationType ,数据库之间的关系以及Yii AR支持的几种关系,可以定义下表:

20130111003

整个模板也是采用主-从模板的方式 ,主模板枚举EntityManager中的每个Entity,然后调用子模板为每个表生成AR类:

  1. public void Generate()
  2. {
  3. EntityManager entityManager = CreateEntityManager();
  4. foreach(IEntity entity in entityManager.Entities)
  5. {
  6. if (!(entity is CommandEntity)) {
  7. RenderEntity(entity);
  8. }
  9. }
  10. }
  11.  
  12. ...
  13.  
  14. private void RenderEntity(IEntity entity)
  15. {
  16.  
  17. string folder=@"../models/";
  18. EntityTemplate entityTemplate = this.Create<EntityTemplate>();
  19. entityTemplate.SourceEntity = entity;
  20. entityTemplate.RenderToFile(folder+entity.Name+".php", true);
  21. }
  22.  

子模板则根据每个Entity的Assoications(关系属性)为AR 生成relations函数,

  1. <?php
  2.  
  3. class <%= SourceEntity.Name %> extends CActiveRecord
  4. {
  5. public static function model($className=__CLASS__)
  6. {
  7. return parent::model($className);
  8. }
  9.  
  10. public function tableName()
  11. {
  12. return '<%= SourceEntity.GetSafeName() %>';
  13. }
  14.  
  15. <%if (SourceEntity.Associations.Count>0){ %>
  16. public function relations()
  17. {
  18. return array(
  19. <% IEnumerable<IAssociation> associations = SourceEntity.Associations; %>
  20. <% foreach(IAssociation association in associations) { %>
  21. <% if(association.Entity.Name!=association.ForeignEntity.Name) {%>
  22. <% if (association.AssociationType == AssociationType.ManyToOne
  23. || association.AssociationType==AssociationType.ManyToZeroOrOne) { %>
  24. '<%= ToCameral(association.Name) %>'=>array(self::BELONGS_TO,
  25. '<%= association.ForeignEntity.Name %>',
  26. <%=GetBelongToKey(association) %>
  27. <% } %>
  28. <% if (association.AssociationType == AssociationType.OneToMany
  29. || association.AssociationType==AssociationType.ZeroOrOneToMany) { %>
  30. '<%= ToCameral(association.Name) %>'=>array(self::HAS_MANY,
  31. '<%= association.ForeignEntity.Name %>',
  32. <%=GetKey(association) %>
  33. <% } %>
  34. <% if (association.AssociationType == AssociationType.OneToOne
  35. || association.AssociationType==AssociationType.OneToZeroOrOne) { %>
  36. '<%= ToCameral(association.Name) %>'=>array(self::HAS_ONE,
  37. '<%= association.ForeignEntity.Name %>',
  38. <%=GetKey(association) %>
  39. <% } %>
  40. <% if (association.AssociationType == AssociationType.ManyToMany) { %>
  41. '<%= ToCameral(association.Name) %>'=>array(self::MANY_MANY,
  42. '<%= association.IntermediaryAssociation.Entity.Name %>',
  43. <%=GetManyToManyKey(association) %>
  44. <% } %>
  45. <% } %>
  46. <% } %>
  47. );
  48. }
  49. <% } %>
  50. }
  51.  
  52. ?>
  53.  
  54. <script runat="template">
  55.  
  56. public string ToCameral(string name)
  57. {
  58. return StringUtil.ToCamelCase(name);
  59. }
  60.  
  61. public string GetKey(IAssociation association)
  62. {
  63. string retString=string.Empty;
  64.  
  65. if(association.Properties.Count>1)
  66. {
  67. retString="array(";
  68. foreach (AssociationProperty associationProperty in association.Properties)
  69. {
  70. retString+="'"+associationProperty.ForeignProperty.GetSafeName()+"',";
  71. }
  72. retString+="),";
  73. }else{
  74. foreach (AssociationProperty associationProperty in association.Properties)
  75. {
  76. retString+="'"+associationProperty.ForeignProperty.GetSafeName()+"'),";
  77. }
  78.  
  79. }
  80. return retString;
  81. }
  82.  
  83. public string GetBelongToKey(IAssociation association)
  84. {
  85. string retString=string.Empty;
  86.  
  87. if(association.Properties.Count>1)
  88. {
  89. retString="array(";
  90. foreach (AssociationProperty associationProperty in association.Properties)
  91. {
  92. retString+="'"+associationProperty.Property.GetSafeName()+"',";
  93. }
  94. retString+="),";
  95. }else{
  96. foreach (AssociationProperty associationProperty in association.Properties)
  97. {
  98. retString+="'"+associationProperty.Property.GetSafeName()+"'),";
  99. }
  100.  
  101. }
  102. return retString;
  103. }
  104.  
  105. public string GetManyToManyKey(IAssociation association)
  106. {
  107.  
  108. string retString="'"+association.ForeignEntity.GetSafeName()+"(";
  109.  
  110. foreach (AssociationProperty associationProperty in association.Properties)
  111. {
  112. retString+=associationProperty.ForeignProperty.GetSafeName()+",";
  113. }
  114. IAssociation intermidateAssociation=association.IntermediaryAssociation;
  115. if(intermidateAssociation!=null)
  116. {
  117. foreach (AssociationProperty associationProperty in intermidateAssociation.Properties)
  118. {
  119. retString+=associationProperty.ForeignProperty.GetSafeName()+",";
  120. }
  121. }
  122.  
  123. retString=retString.Substring(0,retString.Length-1);
  124. retString+=")'),";
  125. return retString;
  126. }
  127. </script>

然后generated output 就可以为数据库的表生成对应的AR类,比如生成的Track类

  1. class Track extends CActiveRecord
  2. {
  3. public static function model($className=__CLASS__)
  4. {
  5. return parent::model($className);
  6. }
  7.  
  8. public function tableName()
  9. {
  10. return 'track';
  11. }
  12.  
  13. public function relations()
  14. {
  15. return array(
  16. 'album'=>array(self::BELONGS_TO,'Album','AlbumId'),
  17. 'genre'=>array(self::BELONGS_TO,'Genre','GenreId'),
  18. 'mediatype'=>array(self::BELONGS_TO,'Mediatype','MediaTypeId'),
  19. 'invoicelines'=>array(self::HAS_MANY,'Invoiceline','TrackId'),
  20. 'playlists'=>array(self::MANY_MANY,'Playlist','playlisttrack(TrackId,PlaylistId)'),
  21. );
  22. }
  23. }

如果实在看不懂也无所谓,可以直接使用该模板,只要设置数据源 ,如果数据库的表有前缀,比如Wordpress的表有wp_ 可以设置表前缀(不是必须的)

20130111004

下载 ,如果需要使用的模板,直接把项目中protected下的codesmith 目录拷贝到你自己的项目中,然后为codesmith.csp 配置数据源(或者还有表前缀),然后生成代码即可。

20130111005

下载