14.6 生成代码资源

开发企业应用程序时,常常会遇到很少变化的引用数据,它们在代码中表示为一个枚举类型。使数据库中的数据与枚举类型的值同步是一个耗时、重复的任务,非常适于用T4模板自动完成。本节的模板将连接AdventureWorks示例数据库,并根据Person.ContactType表的内容创建一个枚举。

C#

      <<#@ template debug="false" hostspecific="false" language="C#" #>
      <#@ output extension=".generated.cs" #>
      <#@ assembly name="System.Data" #>
      <#@ import namespace="System.Data.SqlClient" #>
      <#@ import namespace="System.Text.RegularExpressions" #>
      <#
      var connectionString = "Data Source=.\\SQLEXPRESS; Initial Catalog=AdventureWorks;"
         + "Integrated Security=true;";
      var sqlString = "SELECT ContactTypeID, [Name] FROM [Person].[ContactType]";
      #>
      // This code is generated. Please do not edit it directly
      // If you need to make changes please edit ContactType.tt instead
      namespace AdventureWorks {
        public enum ContactType {
      <#
      using(var conn = new SqlConnection(connectionString))
      using(var cmd = new SqlCommand(sqlString, conn))
      {
        conn.Open();
        var contactTypes = cmd.ExecuteReader();
        while( contactTypes.Read() )
        {
        #>
          <#= ValidIdentifier( contactTypes[1].ToString() ) #> = <#=contactTypes[0]#>,
        <#}
        conn.Close();
      }
      #>
       }
      }
      <#+
        public string ValidIdentifier(string input)
        {
          return  Regex.Replace(input, @"[^a-zA-Z0-9]", String.Empty );
        }
      #>

VB

      <#@ template debug="false" hostspecific="false" language="VB" #>
      <#@ output extension=".generated.vb" #>
      <#@ assembly name="System.Data" #>
      <#@ import namespace="System.Data.SqlClient" #>
      <#@ import namespace="System.Text.RegularExpressions" #>
      <#
      Dim ConnectionString as String = "Data Source=.\SQLEXPRESS; " _
      & "Initial Catalog=AdventureWorks; Integrated Security=true;"
      Dim SqlString as String = "SELECT ContactTypeID,[Name] FROM [Person].[ContactType]"
      #>
      ' This code is generated. Please do not edit it directly
      ' If you need to make changes please edit ContactType.tt instead
      Namespace AdventureWorks
        Enum ContactType
      <#
      Using Conn As New SqlConnection(ConnectionString), _
            Cmd As New SqlCommand(SqlString, Conn)
         Conn.Open()
         Dim ContactTypes As SqlDataReader = Cmd.ExecuteReader()
         While ContactTypes.Read()
      #>
          <#= ValidIdentifier( contactTypes(1).ToString() ) #> = <#=contactTypes(0)#>
      <#
        End While
        Conn.Close()
      End Using
      #>
        End Enum
      End Namespace
      <#+
        Public Function ValidIdentifier(Input as String) As String
          Return Regex.Replace(Input, "[^a-zA-Z0-9]", String.Empty )
        End Function
      #>

上述示例中使用的AdventureWorks数据库可从http://msftdbprodsamples.codeplex.com下载。可在该站点上找到如何安装此数据库的指示,并且可能需要修改用于该示例中的连接字符串,以符合自己的SQL环境。

第一部分由T4指令组成。前两个指令指定模板的语言和输出文件的扩展名。第三个指令把一个程序集附加到生成器上(以访问System.Data.SqlClient名称空间),最后两个指令把模板代码需要的名称空间导入模板。

下一部分是一个T4 Statement块,它包含一些模板要使用的变量。把它们放在模板文件的顶部,将便于在需要修改时找到它们。

在变量声明的后面是一个T4 Text块,其中包含一些解释性的注释、一个名称空间和一个枚举声明。它们会全部复制到生成的输出文件中。通常最好在生成的文件中提供一个注释,说明数据的来源和编辑方式。这可以防止重新生成文件后删除修改时的意外事故。

模板的剩余部分是一个Statement块。这个块使用第一个Statement块中定义的变量,创建并打开一个与AdventureWorks数据库的连接。接着查询数据库,用数据读取器检索需要的数据。

对于每条从数据库中提取的记录,都产生一个Text块。这个Text块包含两个用等号隔开的Expression块。第二个表达式仅把Contact类型的ID添加到生成的输出文件中。第一个表达式调用在Class Feature块中定义的辅助方法ValidIdentifier,从Contact Type Name中删除所有无效的字符,给每个Contact类型创建一个有效的标识符。

生成的输出文件显示在下面的清单中。与生成它的脚本相比,最终结果看起来相当简单,但这有点欺骗性。在ContactType表中添加、删除数据行时,T4模板可以保持不变。实际上,数据库中的项可以完全重新排序,而代码仍能编译。略加修改,这个脚本甚至可以用于一次从许多不同的表中生成枚举类型。

C#

      // This code is generated. Please do not edit it directly
      // If you need to make changes please edit ContactType.tt instead
      namespace AdventureWorks {
        public enum ContactType {
            AccountingManager = 1,
            AssistantSalesAgent = 2,
            AssistantSalesRepresentative = 3,
            CoordinatorForeignMarkets = 4,
            ExportAdministrator = 5,
            InternationalMarketingManager = 6,
            MarketingAssistant = 7,
            MarketingManager = 8,
            MarketingRepresentative = 9,
            OrderAdministrator = 10,
            Owner = 11,
            OwnerMarketingAssistant = 12,
            ProductManager = 13,
            PurchasingAgent = 14,
            PurchasingManager = 15,
            RegionalAccountRepresentative = 16,
            SalesAgent = 17,
            SalesAssociate = 18,
            SalesManager = 19,
            SalesRepresentative = 20,
          }
      }

VB

      ' This code is generated. Please do not edit it directly
      ' If you need to make changes please edit ContactType.tt instead
      Namespace AdventureWorks
        Enum ContactType
          AccountingManager = 1
          AssistantSalesAgent = 2
          AssistantSalesRepresentative = 3
          CoordinatorForeignMarkets = 4
          ExportAdministrator = 5
          InternationalMarketingManager = 6
          MarketingAssistant = 7
          MarketingManager = 8
          MarketingRepresentative = 9
          OrderAdministrator = 10
          Owner = 11
          OwnerMarketingAssistant = 12
          ProductManager = 13
          PurchasingAgent = 14
          PurchasingManager = 15
          RegionalAccountRepresentative = 16
          SalesAgent = 17
          SalesAssociate = 18
          SalesManager = 19
          SalesRepresentative = 20
        End Enum
      End Namespace