ORM框架-工具-产品开发之四 开发代码生成器 Template Studio Development (一)
今天進入ORM工具開發系列的代碼生成工具的開發。現在流行的代碼生成工具,一般是基于模板的。T4,Code Smith在基于模板的代碼生成方面相當流行。ORM工具,需要從不同的數據庫中讀取元數據,調用代碼生成模板,生成代碼。
先來看一下代碼生成器的界面,邊看邊說。
界面是采用文章《Management Console 工具管理類軟件通用開發框架(開放源碼)》中提到的代碼框架,加上停靠的Output窗口,輸出編譯錯誤和調試信息,Properties窗體,用于解析屬性,設置屬性值,Server Explorer用來連接數據庫獲取元數據,調用代碼生成模板。
?
文件格式
模板的文件的第一種格式是《Write your own Code Generator or Template Engine in .NET》中提到的技術,把引用的程序集,和引用的命名空間,都放到文件中。這樣,文件肯定不能是文本格式的,它是模板對象的Xml序列化格式。
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." %>
這樣說可能還不太明白,請看一下面的圖
解析引擎會分析到模板有三個參數,會在右邊的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; }
????? }
}
解析后的效果是這樣
為什么要這么定義? 我來簡化一下它的寫法,本來是可以這樣寫的,我初始的想法是這樣的版本
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”);
把這三個過程細化,就是模板代碼生成的原理。在《模板代碼生成的原型》一節,再詳細講解動態編譯。
轉載于:https://www.cnblogs.com/JamesLi2015/archive/2011/09/05/2167000.html
總結
以上是生活随笔為你收集整理的ORM框架-工具-产品开发之四 开发代码生成器 Template Studio Development (一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java中日期加减计算(转)
- 下一篇: httpclient4 中文版帮助文档,