Today, I entered the ORM tool development seriesCodeDevelopment of generation tools. Currently, popular code generation tools are generally template-based. T4 and code Smith are quite popular in template-based code generation. The ORM tool reads metadata from different databases, calls the code generation template, and generates code.
Let's take a look at the Code Generator interface and talk about it.
The interface isArticleThe code framework mentioned in the management console tool management software general development framework (Open Source Code), coupled with the output window that is docked, outputs compilation errors and debugging information. The properties form is used to parse attributes, set the property value. Server Explorer is used to connect to the database to obtain metadata and call the code generation template.
File Format
The first format of the template file is the technology mentioned in write your own code generator or template engine in. net.ProgramSet and referenced namespace are put in the file. In this way, the file must not be in the text format. It is the XML serialization format of the template object.
Input is the template content and the Assembly referenced by references. Using_libraries is the import namespace, C # is using, and VB. NET is import.
This format is used when I start to develop a template generator. However, it was later found that the efficiency was poor. Every time a file was opened or closed, it was required to be serialized into base64 encoding, which took time. In addition, this format does not support notepad for editing, not just files, it is not convenient to edit.
After the first format is improved, the format of the Code Smith format template is learned and the text format is used as the template file format. It looks like this.
<% @ Assembly name = "testclasslibrary" %>
<% @ Import namespace = "EPN. Common" %>
Public class Jack
{
Public void main ()
{
Int key = system. Console. readkey ();
}
}
In this way, the referenced assembly and imported namespace are put into the template. This simplifies the template writing, but adds a lot of compilation settings.
Template syntax
The basic principle of the template builder is to generate a template as a type, call the code of the Assembly generated by it, and enter the result. According to the template syntax of llbl Gen 3. X and the template syntax of code Smith, the template syntax of the ASP. NET page file looks like this
<% @ 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 %>
<% }%>
This is taken from the code Smith syntax. The template studio I wrote can also parse templates with code Smith, with similar syntax.
The property tag is used to define properties to accept user input and apply it to the template. This value will be resolved to a variable of the type during template parsing for ease of use. Three types of attributes are supported: Boolean, string, and int32. The syntax is as follows:
<% @ 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." %>
This may not be clear yet. Please take a look at the figure above.
The parsing engine analyzes that the template has three parameters, which are displayed in the properties form on the right, and provides values so that users can enter them again to achieve dynamic variables. When running the template, the value is uploaded to the code generated by the template.
Let's take a look at how to define type variables instead of simple types. The template is as follows:
<% @ 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 %>
<% }%>
}
}
Like adding a common simple type variable, mathprogram is the type name, math is the attribute name, because it is the type, so add assembly to specify the type of the Assembly, import specifies the namespace of the type. The two values can uniquely determine a type.
Let's look at the definition of the type 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 ;}
}
}
The parsed results are as follows:
Why is this defined? I want to simplify the writing of it. It could have been written in this way. My initial idea is this version.
Public class mathprogram
{
Public mathprogram (string system, string Application)
{
_ Systemname = system;
_ Applictionname = application;
}
Public String _ applictionname;
Public String _ systemname;
}
Propertygrid control, does not know the variable member definition, only knows the attribute, refactor-> encapsulate field becomes the attribute.
[Browsable (true)] is added to indicate that [category ("text")] is the property declaration parsed from propertygrid control,
[Defaultvalue ("")] and [description ("namespace for this class")] are the same principles.
The definition of mathprogram also adds the declarative feature [typeconverter (typeof (expandconverter)] to expand the display in propertygrid. If this feature is not available, it is read-only in propertygrid and is displayed in gray.
The template syntax content to be mentioned is an ASP. NET-style code snippet, such
<% For (INT I = 0; I <10; I ++) {%>
<% = Math. applictionname %> <% = I %>
<% }%>
The code between <% and %> is automatically added to the generated type for parsing into executable code.
Rad component used to interact with the. NET Property Window
Parsing, generating, and retrieving user-INPUT attribute values are an important task of the template generator. First look at this example
The code above is parsed into the properties form. The following Code explains how it works.
Mathprogram builder = new mathprogram ();
Builder. applicationname = "template Studio ";
Builder. systemname = "ORM ";
Propertygrid1.selectedobject = builder;
Applicationname and systemname are the attribute names of encapsulate field and contain browsable and category metadata.
If you put this type in another type, the Code is as follows:
Builder = new Builder ();
Builder. Category = "cnblogs ";
Builder. Math = new mathprogram ();
Builder. Math. applicationname = "template Studio ";
Builder. Math. systemname = "ORM ";
Propertygrid1.selectedobject = builder;
To set the value in the properties form, you need to write typeconverter to it. Generally, public code like this can achieve the goal.
Public class commonconverter: typeconverter
{
Public override propertydescriptorcollection
getproperties (itypedescriptorcontext context,
object value,
attribute [] filter)
{< br> return typedescriptor. getproperties (value, filter);
}
Public override bool getpropertiessupported (
Itypedescriptorcontext context)
{
Return true;
}
}
The expandconverter type converter applied in the previous section, which has an additional processing function to display mathprogram as a string, for example, parsing it from "Orm, template Studio" into a mathprogram object, this is the credit of typeconverter.
Dynamic Compilation
The template generation process is to generate a process that uses the Template Name as the type name and template code as the embedded method. In the code process, most of the Code is constructing this type, so that the compiler can compile it into an assembly and then call its render method. The method code is as follows:
Assembly = createassembly (sourcetemplate, language, parameters );
Type type = assembly. GetType ("template ");
Invokemethod (type, "render ");
The three processes are refined by the principle of template code generation. In the template code generated prototype section, we will explain in detail the dynamic compilation.