zoukankan      html  css  js  c++  java
  • ORM框架工具产品开发之四 开发代码生成器 Template Studio Development (一)

    今天进入ORM工具开发系列的代码生成工具的开发。现在流行的代码生成工具,一般是基于模板的。T4,Code Smith在基于模板的代码生成方面相当流行。ORM工具,需要从不同的数据库中读取元数据,调用代码生成模板,生成代码。

    先来看一下代码生成器的界面,边看边说。

    image

    界面是采用文章《Management Console 工具管理类软件通用开发框架(开放源码)》中提到的代码框架,加上停靠的Output窗口,输出编译错误和调试信息,Properties窗体,用于解析属性,设置属性值,Server Explorer用来连接数据库获取元数据,调用代码生成模板。

    文件格式

    模板的文件的第一种格式是《Write your own Code Generator or Template Engine in .NET》中提到的技术,把引用的程序集,和引用的命名空间,都放到文件中。这样,文件肯定不能是文本格式的,它是模板对象的Xml序列化格式。

    image

    Input是模板的内容,References引用的程序集。Using_Libraries是导入命名空间,C#为using,VB.NET是Import。

    这种格式,在开始开发模板生成器时,我采用这种格式。但是,后来发现效率不好,每次打开和关闭文件时,都需要序列化为Base64编码,这需要耗费时间,另外,这种格式不支持Notepad来编辑,不是纯文件,不方便编辑。

    经过对第一种格式的改良,学习Code Smith格式模板的格式,采用文本格式作为模板文件的格式。看起来是这样

    <%@ Assembly Name="TestClassLibrary" %>
    <%@ Import Namespace="EPN.Common" %>

    public class Jack
    {
        public void Main()
        {
            int key=System.Console.ReadKey();
        }
    }

    这样,将引用的程序集和导入的命名空间,放到模板中。这样,简化了模板的写法,但是,会增加很多编译的设置工作。

    模板语法

    模板生成器的基本原理是,将模板生成为一个类型,调用它生成的程序集的代码,输入结果。根据接触到的LLBL Gen 3.x的模板语法和Code Smith的模板语法,ASP.NET的Page页面文件,模板的语法看起来是这样

    <%@ Property Name="IncludeDrop" Type="System.Boolean" Default="True" Category="Options" Description="If true drop statements will be generated to drop existing stored procedures." %>
    <%@ Assembly Name="System.Data" %>
    <%@ Import Namespace="System.Data" %>

    <%
    string property1= "DemoClass";

    %>
    Current DateTime is: <%=DateTime.Now%>
    Test Property :<%=IncludeDrop%>
    <% for(int i=0;i<10;i++) { %>
             <%=Math.ApplictionName%> <%=i%>
            <% } %>

    这里取自于Code Smith的语法。我写的这个Template Studio也可以解析有Code Smith的模板,语法与之相似。

    Property 标签用来定义属性,以便接受用户的输入,应用到模板中。这个值,在解析模板时,会解析有成为类型的变量,以便于使用。 这里有支持三种类型的属性,Boolean,String,Int32。语法如下所示

    <%@ Property Name="IncludeInsert" Type="System.Boolean" Default="True" Category="Options" Description="If true insert statements will be generated." %>
    <%@ Property Name="IncludeUpdate" Type="System.String" Default="CNBLOGS" Category="Options" Description="If true update statements will be generated." %>
    <%@ Property Name="IncludeDelete" Type="System.Int32" Default="123" Category="Options" Description="If true delete statements will be generated." %>

    这样说可能还不太明白,请看一下面的图

    image

    解析引擎会分析到模板有三个参数,会在右边的Properties窗体中显示出来,并且提供值,让用户重新输入,这样达到动态变量的目的。在运行模板时,会把这个值传到模板生成的代码中去。

    再来看,如何定义类型变量,而不是简单类型。模板举例如下

    <%@ Property Name="Math" Type="MathProgram"  Category="Text"Description="Namespace for this class" %>

    <%@ Assembly Name="TestClassLibrary" %>
    <%@ Import Namespace="EPN.Common" %>

    public class Jack
    {
        public void Main()
        {
            int key=System.Console.ReadKey();
           <%=Math.SystemName%>
            <% for(int i=0;i<10;i++) { %>
             <%=Math.ApplictionName%> <%=i%>
            <% } %>
        }
    }

    和添加普通的简单类型变量一样,MathProgram是类型名称,Math是属性名,因为是类型,所以加上Assembly以指明类型所在的程序集,Import指明类型所在的命名空间,这两个值可以唯一确定一个类型。

    来看类型MathProgram的定义,

    [TypeConverter(typeof(ExpandConverter))]
    public class MathProgram
    {
          public MathProgram(string system,string application)
          {
              _SystemName = system;
              _ApplictionName = application;
          }

           public MathProgram(){}

           private string _SystemName;
          [Browsable(true)]
          [Category("Text")]
          [DefaultValue("")]
          [Description("Namespace for this class")]
          public string SystemName
          {
              get { return _SystemName; }
              set { _SystemName = value; }
          }

          private string _ApplictionName;

          [Browsable(true)]
          [Category("Text")]
          [DefaultValue("")]
          [Description("Namespace for this class")]
          public string ApplictionName
          {
              get { return _ApplictionName; }
              set { _ApplictionName = value; }
          }

    }

    解析后的效果是这样

    image

    为什么要这么定义? 我来简化一下它的写法,本来是可以这样写的,我初始的想法是这样的版本

    public class MathProgram
    {
          public MathProgram(string system,string application)
          {
              _SystemName = system;
              _ApplictionName = application;
          }

          public string _ApplictionName;

            public string _SystemName;

    }

    PropertyGrid控件,不认识变量成员定义,只认识属性,Refactor—>Encapsulate Field变成属性。
    还要加上[Browsable(true)],以指示在PropertyGrid控件中显示,[Category("Text")]是解析自它的属性声明,
    [DefaultValue("")]和[Description("Namespace for this class")]也是一样的原理。

    MathProgram的定义还加上了声明式的特性[TypeConverter(typeof(ExpandConverter))],目的是为了在PropertyGrid中展开显示,如果没有这个特性,它在PropertyGrid中是只读的,显示为灰色。

    最后要提到的模板语法内容,是ASP.NET样式的代码片段,像这样

    <% for(int i=0;i<10;i++) { %>
             <%=Math.ApplictionName%> <%=i%>
            <% } %>

    <%和%>之间的代码,会原封不动的Render到生成的类型中,以用于解析成可执行的代码。

    与.NET属性窗口交互的RAD组件

    属性的解析,生成,获取用户输入的属性值,是模板生成器的一项重要的工作。先看这个例子

    上面解析到Properties窗体中的代码,下面的代码解释它的工作原理

    MathProgram builder = new MathProgram();
    builder.ApplicationName= "Template Studio";
    builder.SystemName= " ORM";
    propertyGrid1.SelectedObject = builder;

    ApplicationName和SystemName是Encapsulate Field的属性名称,并且带有Browsable,Category元数据。

    如果把这个类型放到另一个类型中去,代码像这样

    Builder builder = new Builder();
    builder.Category = "CNBLOGS";
    builder.Math = new MathProgram();
    builder.Math.ApplicationName= "Template Studio";
    builder.Math.SystemName=" ORM";
    propertyGrid1.SelectedObject = builder;

    为了能在Properties窗体中设置值,要给它写TypeConverter。一般像这样的公共代码就可以达到目的

    public class CommonConverter : TypeConverter
    {

           public override PropertyDescriptorCollection
               GetProperties(ITypeDescriptorContext context,
                    object value,
                    Attribute[] filter)
           {
               return TypeDescriptor.GetProperties(value, filter);
           }

           public override bool GetPropertiesSupported(
                    ITypeDescriptorContext context)
           {
               return true;
           }
    }

    上一节中应用到的ExpandConverter类型转换器,它多了一项处理是可以把MathProgram显示为字符串,比如从”ORM,Template Studio”中解析成MathProgram对象,这是TypeConverter的功劳。

    动态编译

    模板的生成过程,就是生成一个以模板名字为类型名称,以模板代码为嵌入方法的过程。在代码过程中,大部分的代码都是在构造这个类型,以便于编译器编译成为程序集,再调用它的Render方法。方法代码如下

    Assembly assembly=CreateAssembly(sourceTemplate,language,parameters);

    Type  type=assembly.GetType(“Template”);

    InvokeMethod(type,”Render”);

    把这三个过程细化,就是模板代码生成的原理。在《模板代码生成的原型》一节,再详细讲解动态编译。

  • 相关阅读:
    Hihocoder 1275 扫地机器人 计算几何
    CodeForces 771C Bear and Tree Jumps 树形DP
    CodeForces 778D Parquet Re-laying 构造
    CodeForces 785E Anton and Permutation 分块
    CodeForces 785D Anton and School
    CodeForces 785C Anton and Fairy Tale 二分
    Hexo Next 接入 google AdSense 广告
    如何统计 Hexo 网站的访问地区和IP
    Design and Implementation of Global Path Planning System for Unmanned Surface Vehicle among Multiple Task Points
    通过ODBC接口访问人大金仓数据库
  • 原文地址:https://www.cnblogs.com/JamesLi2015/p/2167000.html
Copyright © 2011-2022 走看看