Facebook Twitter LinkedIn E-mail
magnify
Home Posts tagged "CodeSmith"

CodeSmith 使用教程(18) 总结

前面基本介绍了CodeSmith的基本用法和编写代码模板的基本方法,这只是CodeSmith功能的一部分,其它部分可以参考CodeSmith文档类文件定义。此外可以参考CodeSmith附带的示例模板。

为便于查询,列出所有文档如下:

  1. CodeSmith 使用教程(1): 概述
  2. CodeSmith 使用教程(2): 编写第一个代码模板
  3. CodeSmith 使用教程(3): 自动生成Yii Framework ActiveRecord类简单模板
  4. CodeSmith 使用教程(4): 基本语法-CodeTemplate 指令
  5. CodeSmith 使用教程(5): 基本语法-使用注释
  6. CodeSmith 使用教程(6): 基本语法-声明和使用属性
  7. CodeSmith 使用教程(7): 基本语法-转义Asp.Net标记
  8. CodeSmith 使用教程(8): CodeTemplate对象
  9. CodeSmith 使用教程(9): Progress对象
  10. CodeSmith 使用教程(10): CodeTemplateInfo 对象
  11. CodeSmith 使用教程(11) 引用其它文件或.Net 类库
  12. CodeSmith 使用教程(12) 使用主从代码模板
  13. CodeSmith 使用教程(13) 调试
  14. CodeSmith 使用教程(14) 使用SchemaExplorer来获取数据库定义
  15. CodeSmith 使用教程(15) 为Yii Framework 创建生成ActiveRecord的代码模板
  16. CodeSmith 使用教程(16) 使用XMLProperty
  17. CodeSmith 使用教程(17) Merge策略
 

CodeSmith 使用教程(17) Merge策略

前面介绍了CodeSmith使用的基本用法,通过代码模板来生成代码,但如果你修改了自动生成的代码,再次使用代码模板生成代码后,你修改的代码也就丢失了,CodeSmith 支持多种“合并(Merge)”来解决这个问题,以保留你自己修该过的部分。

CodeSmith支持如下三种“合并策略”:

不过这些策略主要是针对C#,VB这些支持Region的语言,对于其它语言可能就需要使用其它方法,比如自定义Merge策略,CodeSmith允许通过CodeSmith.Engine.IMergeStrategy 来扩展“合并”策略,本人推荐CodeSmith的一个原因就是CodeSmith提供了很多接口而不仅仅是一个工具,比如除了CodeSmith支持的属性,XML属性,你也可以通过 CodeSmith.CustomProperties来自定义属性种类,除了CodeSmith支持的数据源种类(MySQL,Oracle),你也可以通过自定义的Schema Provider 支持新的数据库类型或是其它数据类型。

InsertRegion策略

InsertRegion 顾名思义,就是在源码中定义一个Region,然后让CodeSmith自动生成的代码只插入到该区域,而在区域外的代码CodeSmith不会去碰它们,从而实现了自定义的代码和自动生成代码的合并。

PreserveRegion策略

PreserveRegion 是定义多个区域,然后通知CodeSmith保持这些区域代码不变,自动创建的代码添加到这些区域的外面,和InsertRegion作用相反。

下面还是借用CodeSmith 自带的Merge示例说明一下这两种策略的基本用法:

首先是InsertRegion策略,定义一个类文件InsertRegionSample.cs

public class InsertRegionsSample
{

    public void SomeCustomMethod()
	{
        // This is my custom code that I want to preserve.
        // I can make changes to it and my changes will
        // not be overwritten.
	}

	#region Sample Generated Region
// This region generated by CodeSmith on Saturday, 12 January 2013
	#endregion
}

其中定义了一个Region,名为Sample Generated Region ,准备让CodeSmith查入代码,编写一个简单的代码模板,插入当前时间:

<%@ Template Language="C#" TargetLanguage="C#"   Description="Demonstrates using an InsertRegion merge strategy in C#." %>
// This region generated by CodeSmith on <%= DateTime.Now.ToLongDateString() %>

然后通过CodeSmith项目为模板设置Merge策略:

20130112002

选择InsertRegion策略, 然后设置要插入的RegionName。

生成后的代码如下:

public class InsertRegionsSample
{

    public void SomeCustomMethod()
	{
        // This is my custom code that I want to preserve.
        // I can make changes to it and my changes will
        // not be overwritten.
	}

	#region Sample Generated Region
// This region generated by CodeSmith on Saturday, 12 January 2013
	#endregion
}

可以看到CodeSmith只在Region 处插入代码,而该Region外的部分保持不变。

类似的PreserveRegions策略,代码和模板定义如下:
PreserveRegionsSample.cs

public class PreserveRegionsSample
{

#region "Custom Region 1"

	// This is a place holder for your custom code.
	// It must exist so that CodeSmith knows where
	// to put the custom code that will be parsed
	// from the target source file.
	// The region name is used to match up the regions
	// and determine where each region of custom code
	// should be inserted into the merge result.

#endregion

    public void SomeGeneratedMethod()
	{

        // This section and all other non-custom code
        // regions will be overwritten during each
        // template execution.
        // Current Date: Saturday, 12 January 2013
	}

#region "Custom Region 2"

    // The contents of this region will also be preserved
    // during generation.

#endregion

}

模板定义如下:

<%@ Template Language="C#" TargetLanguage="C#"    Description="Demonstrates using a PreserveRegions merge strategy in C#." %>
public class PreserveRegionsSample
{

#region "Custom Region 1"

	// This is a place holder for your custom code.
	// It must exist so that CodeSmith knows where
	// to put the custom code that will be parsed
	// from the target source file.
	// The region name is used to match up the regions
	// and determine where each region of custom code
	// should be inserted into the merge result.

#endregion

    public void SomeGeneratedMethod()
	{

        // This section and all other non-custom code
        // regions will be overwritten during each
        // template execution.
        // Current Date: <%= DateTime.Now.ToLongDateString() %>
	}

#region "Custom Region 2"

    // The contents of this region will also be preserved
    // during generation.

#endregion

}

模板中也定义了两个区域,然后为该模板设置Merge策略,使用PreserveRegion时可能有多个Region需要保留,因此可以使用RegX来定义要保留的Region:
20130112003

本例下载

InsertClass 策略用在给以重载的代码中插入自动生成的代码,挺起来和InsertRegion功能很类似,的确也是如此,但InsertClass支持更多的配置,可以实现更加灵活和强大的功能。

它支持的配置有:

Language

String, Required

只支持VB和C#

ClassName String, Required 需插入代码的类名.
PreserveClassAttributes Boolean, defaults to False 是否保留类已有的Attributes,缺省CodeSmith替代类原来的Attributes
OnlyInsertMatchingClass Boolean, defaults to False 是否只插入匹配的类定义中
MergeImports Boolean, defaults to False 是否合并Import语句
NotFoundAction Enum, defaults to None 如果指定的类没找到后的行动,可以None,InsertAtBottom,InsertInParent几种选项
NotFoundParent String, no default 如果指定NotFoundAction为InsertInParent对应的父类名称.

比如使用如下配置:

Language: C#
ClassName: “Pet”
PreserveClassAttributes: True
OnlyInsertMatchingClass: True
MergeImports: True

现有类定义:

using System;
using System.ComponentModel.DataAnnotations;
namespace Petshop
{
    [ScaffoldTable(true)]
    public class Pet
    {
        public int Age { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
}

自动生成的代码如下:

using System;
using System.Text;
namespace Petshop
{
    public class Pet
    {
        public string FirstName { get; set; }

        public string LastName { get; set; }

        public string FullName
        {
            get { return String.Format("{0} {1}", FirstName, LastName); }

        }
    }
}

使用InsertClass 合并后的代码如下:

using System;
using System.ComponentModel.DataAnnotations;
using System.Text;
namespace Petshop
{
    [ScaffoldTable(true)]
    public class Pet
    {
        public string FirstName { get; set; }

        public string LastName { get; set; }

        public string FullName
        {
            get { return String.Format("{0} {1}", FirstName, LastName); }

        }
    }
}
 

CodeSmith 使用教程(16) 使用XMLProperty

在前面CodeSmith 使用教程(6): 基本语法-声明和使用属性 介绍了CodeSmith中使用属性的基本方法,模板中的属性是通过Property指令来定义。

CodeSmith 也支持使用XML文档来定义属性,可以把一些配置属性定义到XML文件中,定义XML的属性是使用XmlProperty来定义:

<%@ XmlProperty Name="PurchaseOrder"
   Schema="PO.xsd"
   Optional="False"
   Category="Data"
   Description="Purchase Order to generate packing list for." %>

XmlProperty 指令可以有多个参数,除Name为必须的外,其它的参考都是可选的。

属性参数的介绍:

  • Name:模版使用的参数的名称,必须为有效的模板语言名称,比如使用C#,Name必须为有效的C#变量名。但提供XML 的Schema文件时,这个变量的类型为一个XmlDocument实例。
  • Schema:XML属性对应的Schema文件名,可以用来校验存放XML属性的XML文件是否有效,如果提供了Schema 文件,CodeSmith在代码模板中支持IntelliSense。
  • Default:设置默认值。
  • Category:用来说明这个属性在CodeSmith Explorer的属性面板中显示成什么类型,例如下拉选择、直接输入等。
  • Description:在属性面板中对于这个属性的描述。
  • Optional:设置这个属性是否是必须的,设置为True表明这个参数值可有可无,设置为False则这个参数必须有值。
  • OnChanged 为属性发生变化时定义事件处理代码。
  • RootElement: 指定XML根元素的相对路径。

本例使用CodeSmith自带的一个例子,使用PurchaseOrder.xsd ,XML 的定义如下:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema targetNamespace="http://www.codesmithtools.com/purchaseorder"
elementFormDefault="qualified"
xmlns="http://www.codesmithtools.com/purchaseorder"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="PurchaseOrder">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="OrderDate" type="xs:string" minOccurs="1" maxOccurs="1" />
        <xs:element name="SubTotal" type="xs:string" minOccurs="1" maxOccurs="1" />
        <xs:element name="ShipCost" type="xs:string" minOccurs="0" maxOccurs="1" />
        <xs:element name="TotalCost" type="xs:string" minOccurs="1" maxOccurs="1" />
        <xs:element name="ShipTo" minOccurs="0" maxOccurs="1">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="Line1" type="xs:string" minOccurs="0" maxOccurs="1" />
              <xs:element name="City" type="xs:string" minOccurs="0" maxOccurs="1" />
              <xs:element name="State" type="xs:string" minOccurs="0" maxOccurs="1" />
              <xs:element name="Zip" type="xs:string" minOccurs="0" maxOccurs="1" />
            </xs:sequence>
            <xs:attribute name="Name" type="xs:string" />
          </xs:complexType>
        </xs:element>
        <xs:element name="Items" minOccurs="0" maxOccurs="1">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="OrderedItem" minOccurs="0" maxOccurs="unbounded">
                <xs:complexType>
                  <xs:sequence>
                    <xs:element name="ItemName" type="xs:string" minOccurs="1" maxOccurs="1" />
                    <xs:element name="Description" type="xs:string" minOccurs="0" maxOccurs="1" />
                    <xs:element name="UnitPrice" type="xs:string" minOccurs="1" maxOccurs="1" />
                    <xs:element name="Quantity" type="xs:string" minOccurs="1" maxOccurs="1" />
                    <xs:element name="LineTotal" type="xs:string" minOccurs="1" maxOccurs="1" />
                  </xs:sequence>
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

与这个XML Schema配合使用的用来存放XML属性的XML文件为SamplePurchaseOrder.xml ,其定义如下:

<?xml version="1.0"?>
<PurchaseOrder xmlns="http://www.codesmithtools.com/purchaseorder">
  <ShipTo Name="Eric J. Smith">
    <Line1>123 Test Dr.</Line1>
    <City>Dallas</City>
    <State>TX</State>
    <Zip>75075</Zip>
  </ShipTo>
  <OrderDate>05-01-2003</OrderDate>
  <Items>
    <OrderedItem>
      <ItemName>Item #1</ItemName>
      <Description>Item #1 Description</Description>
      <UnitPrice>5.45</UnitPrice>
      <Quantity>3</Quantity>
      <LineTotal>16.35</LineTotal>
    </OrderedItem>
    <OrderedItem>
      <ItemName>Item #2</ItemName>
      <Description>Item #2 Description</Description>
      <UnitPrice>12.75</UnitPrice>
      <Quantity>8</Quantity>
      <LineTotal>102.00</LineTotal>
    </OrderedItem>
  </Items>
  <SubTotal>45.23</SubTotal>
  <ShipCost>5.23</ShipCost>
  <TotalCost>50.46</TotalCost>
</PurchaseOrder>

定义一个简单的模板,把SamplePurchaseOrder.xml 中的内容重新输出,可以在代码模板中定义一个XMLProperty ,其Schema 指定为PurchaseOrder.xsd

<%--
This template demonstates using the XmlProperty directive
--%>
<%@ CodeTemplate Language="C#" TargetLanguage="Text"
  Description="Demonstrates using the Xml serializer." %>
<%@ XmlProperty
   Name="MyPurchaseOrder"
   Schema="PurchaseOrder.xsd"
   Default="SamplePurchaseOrder.xml" %>
This file generated by CodeSmith on <%= DateTime.Now.ToLongDateString() %>

PurchaseOrder:
	Address:
		Name: <%= MyPurchaseOrder.ShipTo.Name %>
		Line1: <%= MyPurchaseOrder.ShipTo.Line1 %>
		City: <%= MyPurchaseOrder.ShipTo.City %>
		State: <%= MyPurchaseOrder.ShipTo.State %>
		Zip: <%= MyPurchaseOrder.ShipTo.Zip %>
	OrderDate: <%= MyPurchaseOrder.OrderDate %>
	Items:
		<% for (int i = 0; i < MyPurchaseOrder.Items.Count; i++) { %>
		<%= i %>:
			ItemName: <%= MyPurchaseOrder.Items[i].ItemName %>
			Description: <%= MyPurchaseOrder.Items[i].Description %>
			UnitPrice: <%= MyPurchaseOrder.Items[i].UnitPrice %>
			Quantity: <%= MyPurchaseOrder.Items[i].Quantity %>
			LineTotal: <%= MyPurchaseOrder.Items[i].LineTotal %>
		<% } %>
	SubTotal: <%= MyPurchaseOrder.SubTotal %>
	ShipCost: <%= MyPurchaseOrder.ShipCost %>
	TotalCost: <%= MyPurchaseOrder.TotalCost %>

模板中定义的XML属性名为MyPurchaseOrder 对应的Schema为PurchaseOrder.xsd ,因此在代码模板可以通过MyPurchaseOrder.ShipTo.Name 的格式来直接引用XML Schema中定义的元素,CoddSmith也支持IntelliSense。
运行该模板,首先需要为MyPurchaseOrder选择合适的XML文件:
20130112001如果选择的文件不符合指定的XML Schema,CodeSmith不允许选择该文件,本例使用预先定义的SamplePurchaseOrder.xml ,生成的文件如下:

This file generated by CodeSmith on Saturday, 12 January 2013

PurchaseOrder:
	Address:
		Name: Eric J. Smith
		Line1: 123 Test Dr.
		City: Dallas
		State: TX
		Zip: 75075
	OrderDate: 05-01-2003
	Items:
		0:
			ItemName: Item #1
			Description: Item #1 Description
			UnitPrice: 5.45
			Quantity: 3
			LineTotal: 16.35
		1:
			ItemName: Item #2
			Description: Item #2 Description
			UnitPrice: 12.75
			Quantity: 8
			LineTotal: 102.00
	SubTotal: 45.23
	ShipCost: 5.23
	TotalCost: 50.46

本例下载

 

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

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类:

public void Generate()
{
   EntityManager entityManager = CreateEntityManager();
   foreach(IEntity entity in entityManager.Entities)
	{
		if (!(entity is CommandEntity)) {
			RenderEntity(entity);
		}
	}
}

...

private void RenderEntity(IEntity entity)
{

	string folder=@"../models/";
	EntityTemplate entityTemplate = this.Create<EntityTemplate>();
	entityTemplate.SourceEntity = entity;
	entityTemplate.RenderToFile(folder+entity.Name+".php", true);
}

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

<?php

class <%= SourceEntity.Name %> extends CActiveRecord
{
	public static function model($className=__CLASS__)
	{
		return parent::model($className);
	}

	public function tableName()
	{
		return '<%= SourceEntity.GetSafeName() %>';
	}

    <%if (SourceEntity.Associations.Count>0){ %>
    public function relations()
	{
		return array(
 		 <% IEnumerable<IAssociation> associations = SourceEntity.Associations; %>
         <% foreach(IAssociation association in associations) { %>
         <% if(association.Entity.Name!=association.ForeignEntity.Name) {%>
            <% if (association.AssociationType == AssociationType.ManyToOne
                || association.AssociationType==AssociationType.ManyToZeroOrOne) { %>
            '<%= ToCameral(association.Name) %>'=>array(self::BELONGS_TO,
			'<%= association.ForeignEntity.Name %>',
			<%=GetBelongToKey(association) %>
            <% } %>
            <% if (association.AssociationType == AssociationType.OneToMany
                || association.AssociationType==AssociationType.ZeroOrOneToMany) { %>
            '<%= ToCameral(association.Name) %>'=>array(self::HAS_MANY,
			'<%= association.ForeignEntity.Name %>',
			<%=GetKey(association) %>
            <% } %>
            <% if (association.AssociationType == AssociationType.OneToOne
                || association.AssociationType==AssociationType.OneToZeroOrOne) { %>
            '<%= ToCameral(association.Name) %>'=>array(self::HAS_ONE,
			'<%= association.ForeignEntity.Name %>',
			<%=GetKey(association) %>
            <% } %>
            <% if (association.AssociationType == AssociationType.ManyToMany) { %>
            '<%= ToCameral(association.Name) %>'=>array(self::MANY_MANY,
			'<%= association.IntermediaryAssociation.Entity.Name %>',
			<%=GetManyToManyKey(association) %>
            <% } %>
         <% } %>
     <% } %>
		);
	}
    <% } %>
}

?>

<script runat="template">

public string ToCameral(string name)
{
    return StringUtil.ToCamelCase(name);
 }

public string GetKey(IAssociation association)
{
    string retString=string.Empty;

    if(association.Properties.Count>1)
    {
        retString="array(";
        foreach (AssociationProperty associationProperty in association.Properties)
        {
            retString+="'"+associationProperty.ForeignProperty.GetSafeName()+"',";
        }
        retString+="),";
    }else{
        foreach (AssociationProperty associationProperty in association.Properties)
        {
            retString+="'"+associationProperty.ForeignProperty.GetSafeName()+"'),";
        }

    }
    return retString;
}

public string GetBelongToKey(IAssociation association)
{
    string retString=string.Empty;

    if(association.Properties.Count>1)
    {
        retString="array(";
        foreach (AssociationProperty associationProperty in association.Properties)
        {
            retString+="'"+associationProperty.Property.GetSafeName()+"',";
        }
        retString+="),";
    }else{
        foreach (AssociationProperty associationProperty in association.Properties)
        {
            retString+="'"+associationProperty.Property.GetSafeName()+"'),";
        }

    }
    return retString;
}

public string GetManyToManyKey(IAssociation association)
{

    string retString="'"+association.ForeignEntity.GetSafeName()+"(";

    foreach (AssociationProperty associationProperty in association.Properties)
    {
        retString+=associationProperty.ForeignProperty.GetSafeName()+",";
    }
    IAssociation intermidateAssociation=association.IntermediaryAssociation;
    if(intermidateAssociation!=null)
    {
           foreach (AssociationProperty associationProperty in intermidateAssociation.Properties)
        {
            retString+=associationProperty.ForeignProperty.GetSafeName()+",";
        }
    }

    retString=retString.Substring(0,retString.Length-1);
    retString+=")'),";
    return retString;
}
</script>

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

class Track extends CActiveRecord
{
	public static function model($className=__CLASS__)
	{
		return parent::model($className);
	}

	public function tableName()
	{
		return 'track';
	}

    public function relations()
	{
		return array(
            'album'=>array(self::BELONGS_TO,'Album','AlbumId'),
            'genre'=>array(self::BELONGS_TO,'Genre','GenreId'),
            'mediatype'=>array(self::BELONGS_TO,'Mediatype','MediaTypeId'),
            'invoicelines'=>array(self::HAS_MANY,'Invoiceline','TrackId'),
            'playlists'=>array(self::MANY_MANY,'Playlist','playlisttrack(TrackId,PlaylistId)'),
		);
	}
}

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

20130111004

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

20130111005

本例下载

 

CodeSmith 使用教程(14) 使用SchemaExplorer来获取数据库定义

在前面例子CodeSmith 使用教程(3): 自动生成Yii Framework ActiveRecord 我们使用了SchemaExplorer 来获取数据的MetaData(数据库Schema 定义)来自动生成Yii Framework的数据库表对应的ActiveRecord定义,本篇较详细的介绍一下的SchemaExplorer的用法,下一篇通过实例除了自动生成自动生成Yii Framework的数据库表对应的ActiveRecord定义外,还自动生成关联ActiveRecord的关系定义,也就是根据数据库表之间的关系(一对多,一对一,多对多)为ActiveRecord定义relations.

CodeSmith的SchemaExplorer定义在Assembly SchemaExplorer.dll 中,其命名空间为SchemaExplorer ,因此如果需要使用CodeSmith的SchemaExplorer 功能的话,需要添加对SchemaExplorer.dll的引用,如下:

<%@ CodeTemplate Language="C#" TargetLanguage="Text" Description="List all database tables" %>
<%@ Property Name="SourceDatabase" Type="SchemaExplorer.DatabaseSchema"
 Category="Context" Description="Database containing the tables." %>
<%@ Assembly Name="SchemaExplorer" %>
<%@ Import Namespace="SchemaExplorer" %>
Tables in database "<%= SourceDatabase %>":
<% for (int i = 0; i < SourceDatabase.Tables.Count; i++) { %>
        <%= SourceDatabase.Tables[i].Name %>
<% } %>

以上代码添加了SchemaExplorer库的引用,并定义了一个属性SourceDatabase,其类型为SchemaExplorer.DatabaseSchema ,在运行这个模板前,必须设置SourceDatabase的值:

20130107002

SourceDatabase属性后显示一个“…”的按钮,表示使用一个附加的专用的编辑器来定义这个属性,点击这个按钮将启动数据库选择对话框:

20130107003

使用这个对象框可以选择已通过Schema Explorer定义过的数据库或者添加新的数据库,通过单击“…”来添加新的数据库定义:

20130107004

如果添加一个新的数据源,SchemaExplorer 打开了 数据源对话库 ,选择合适的数据源类型:

20130107006

CodeSmith缺省支持的数据源类型有很多,包括了常用的ADO, DB2,MySQL,Oracle,PostgreSQL, SQL Server,Sqlite等,也可以自定义新的数据源类型。

本例我们选用SQL Server类型 ,并使用Chinook示例数据库:

20130107007

选择数据库Chinook,显示结果:

Tables in database "Chinook":
        Album
        Artist
        Customer
        Employee
        Genre
        Invoice
        InvoiceLine
        MediaType
        Playlist
        PlaylistTrack
        Track
SchemaExplorer 对应数据库的MetaData(表定义,列定义,主键,外键定义等)定义如下的对象模型,可以在代码模板中使用:
20130107008
上图表示SchemaExplorer 定义了多种对象集合类型,对象类型,比如DatabaseSchema 定义了Commands属性,
其类型为CommandSchemaCollection,这个集合的每项类型为CommandSchema ,对应到数据库定义中的一个命令。
通过这个属性可以获取Command的定义等信息。

使用SchemaExplorer 除了可以使用SchemaExplorer.DatabaseSchema类型来定义属性,还可以通过下面四种类型:
  • TableSchema和 TableSchemaCollection
  • ViewSchema 和 ViewSchemaCollection
  • CommandSchema 和 CommandSchemaCollection
  • ColumnSchema 和 ColumnSchemaCollection

分别对应到表类型,视图类型,命令类型,列类型,比如使用

<%@ Property Name="SourceColumns"  Type="SchemaExplorer.ColumnSchemaCollection"
Category="Database"  Description="Select a set of columns." %>

选择一个表的多个列(ColumnSchemaCollection)

20130107009

对应这些集合类型(比如TableSchemaCollection,ColumnSchemaCollection)缺省的排序是由数据库决定的,因此可能不是排好序的,如果需要排序的话,可以通过Sort方法来实现,比如:

TableSchemaCollection tables = new TableSchemaCollection(SourceDatabase.Tables);
tables.Sort(new PropertyComparer("Name"));

SQL Server数据库可以对表或列定义一些附加的属性(Extended Property)SchemaExplorer 也提供了方法可以来访问/添加 这些Extended Property。
比如SQL Server定义一个扩展属性表示某个列是否为Identity列,这可以通过下面代码来获取:

Identity Field = <% foreach(ColumnSchema cs in SourceTable.Columns) {
    if( ((bool)cs.ExtendedProperties["CS_IsIdentity"].Value) == true) {
        Response.Write(cs.Name);
    }
} %>

更好的方法是使用SchemaExplorer.ExtendedPropertyNames类和ExtendedProperty定义的扩展方法。

例如:

Identity Field = <% foreach(ColumnSchema cs in SourceTable.Columns) {
    if(cs.ExtendedProperties.GetByKey<bool>(SchemaExplorer.ExtendedPropertyNames.IsIdentity) == true) {
        Response.Write(cs.Name);
    }
} %>

CodeSmith缺省支持的扩展属性如下:

表的列

Extended Property Key SchemaExplorer.ExtendedPropertyName Property Name 描述
CS_Description Description The Description
CS_IsRowGuidCol IsRowGuidColumn The Column is a Row Guid
CS_IsIdentity IsIdentity Identity Column
CS_IsComputed IsComputed Computed Column or Index
CS_IsDeterministic IsDeterministic Column is Deterministic
CS_IdentitySeed IdentitySeed Identity Seed
CS_IdentityIncrement IdentityIncrement Identity Increment
CS_SystemType SystemType The System Type (E.G., System.String)
CS_Default DefaultValue The default value

视图的列

Extended Property Key SchemaExplorer.ExtendedPropertyName Property Name 描述
CS_Description Description The Description
CS_IsComputed IsComputed Computed Column or Index
CS_IsDeterministic IsDeterministic Column is Deterministic

命令参数

Extended Property Key SchemaExplorer.ExtendedPropertyName Property Name 描述
CS_Description Description The Description
CS_Default DefaultValue The default value

下一篇通过Table的Key(外键和主键)为Yii Framework 表的ActiveRecord添加Relations

 

CodeSmith 使用教程(13) 调试

编写CodeSmith模板和编写程序一样,也需要进行调试,CodeSmith支持使用CLR’s Just-in-Time debugger调试模板。

要调试模板,首先要在CodeTemplate声明中打开调试Debug=”True”:

<%@ CodeTemplate Language="C#" TargetLanguage="C#" Debug="True" %>

第二步是设置断点:在需要设置断点的地方调用System.Diagnostics.Debugger.Break();

System.Diagnostics.Debugger.Launch();
System.Diagnostics.Debugger.Break();

在调用System.Diagnostics.Debugger.Break();之前需要首先调用System.Diagnostics.Debugger.Launch();

这样在Generate Output时Visual Studio在指定的断点暂停运行:

20130106002

 

此外也可以利用.Net 的System.Diagnostics.Trace 和System.Diagnostics.Debug 添加调试信息。