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”);

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

  • 相关阅读:
    改变this指向的三个函数call, apply, bind的实现
    vscode 前端常用插件推荐
    Java SPI详解
    数据技术分享
    深入SpringMVC视图解析器
    .gitignore文件失效的解决方案
    Spring的事件监听机制
    Spring MVC 配置类 WebMvcConfigurerAdapter
    一起来读Netty In Action之传输(三)
    Tomcat性能调优参数简介
  • 原文地址:https://www.cnblogs.com/JamesLi2015/p/2167000.html
Copyright © 2011-2022 走看看