C# ORM学习笔记:T4入门及生成数据库实体类
一、什么是t4?
1.1、t4简介
t4,即4个t开头的英文字母组合:text template transformation toolkit,是微软官方在visual studio 2008开始使用的代码生成引擎。t4是由一些文本块和控制逻辑组成的混合模板,简单地说,t4可以根据模板生成您想要的文件,如类文件、文本文件、html等等。
vs提供了一套基于t4引擎的代码生成执行环境,由以下程序集构成:
microsoft.visualstudio.texttemplating.10.0.dll
microsoft.visualstudio.texttemplating.interfaces.10.0.dll
microsoft.visualstudio.texttemplating.modeling.10.0.dll
microsoft.visualstudio.texttemplating.vshost.10.0.dll
1.2、t4模板类型
t4模板有两种类型:
1)运行时模板
在应用程序中执行运行时t4文本模板,以便生成文本字符串。
若要创建运行时模板,请向您的项目中添加"运行时文本模板"文件。另外,您还可以添加纯文本文件并将其"自定义工具"属性设置为"texttemplatingfilepreprocessor"。
2)设计时模板
在vs中执行设计时t4文本模板,以便定义应用程序的部分源代码和其它资源。
若要创建设计时模板,请向您的项目中添加"文本模板"文件。 另外,您还可以添加纯文本文件并将其"自定义工具"属性设置为"texttemplatingfilegenerator"。
1.3、插件安装
vs默认的编辑工具无高亮、无提示,错误不容易定位,建议安装tangible t4 editor插件进行编写t4代码。
二、t4 hello world示例
假设有一个控制台应用程序linkto.test.consolet4,现在要输出"hello world"字符串,program代码如下:
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; namespace linkto.test.consolet4 { class program { static void main(string[] args) { console.writeline("hello world"); console.read(); } } }
现在把program.cs文件删除掉,利用t4模板生成一个与上述代码相同的program.cs,操作方法如下:
1)项目右键"添加"->"新建项"->"文本模板",将名称更改为program.tt。
2)program.tt的代码如下:
<#@ output extension=".cs" #> using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; <# string classname = "program"; #> namespace linkto.test.consolet4 { class <#=classname #> { static void main(string[] args) { console.writeline("hello world"); console.read(); } } }
3)点击保存,即可看到program.tt下生成了一个program.cs文件,代码与最初的hello world一样。
三、t4 hello world示例扩展
现在扩展一下hello world示例,在程序中增加两个类:
1)hello类,输出"hello"。
2)world类,输出"world"。
代码如下:
<#@ template debug="false" hostspecific="false" language="c#" #> <#@ assembly name="system.core" #> <#@ import namespace="system.linq" #> <#@ import namespace="system.text" #> <#@ import namespace="system.collections.generic" #> <#@ output extension=".cs" #> using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; <# string classname = "program"; #> <# list<string> classnames = new list<string>() {"hello","world"}; list<string> callmethods = new list<string>(); #> namespace linkto.test.consolet4 { <# foreach (string classname in classnames) { callmethods.add($"{classname}.show();"); #> class <#=classname #> { /// <summary> /// <#=classname #>类show()方法 /// </summary> public static void show() { console.writeline("<#=classname #>"); } } <# } #> class <#=classname #> { static void main(string[] args) { console.writeline("hello world"); <# foreach (string callmethod in callmethods) { #> //<#=callmethod #>方法调用 <#=callmethod #> <# } #> console.read(); } } }
生成文件如下:
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; namespace linkto.test.consolet4 { class hello { /// <summary> /// hello类show()方法 /// </summary> public static void show() { console.writeline("hello"); } } class world { /// <summary> /// world类show()方法 /// </summary> public static void show() { console.writeline("world"); } } class program { static void main(string[] args) { console.writeline("hello world"); //hello.show();方法调用 hello.show(); //world.show();方法调用 world.show(); console.read(); } } }
四、t4模板的基本结构
代码块可分为两种:文本及程序脚本。
4.1、文本:就是需要生成的文本
4.2、程序脚本:内部执行,最终生成想要的文本。t4中<# #>中的部分,都属于程序脚本内容。
为了方便理解,使用"块"(block)来细分语法。块是构成t4模板的基本单元,可以分成5类:指令块(directive block)、文本块(text block)、代码语句块(statement block)、表达式块(expression block)、类特性块(class feature block)。
4.2.1、指令块(directive block)
和asp.net页面的指令一样,它们出现在文件头,通过<#@ … #>表示。其中<#@ template … #>指令是必须的,用于定义模板的基本属性,比如编程语言、基于的文化、是否支持调式等。
指令通常是模板文件或包含的文件中的第一个元素。不应将它们放置在代码块<#...#>内,也不应放置在类功能块<#+...#>之后。
t4 模板指令
<#@ template [language="c#"] [hostspecific="true"] [debug="true"] [inherits="templatebaseclass"] [culture="code"] [compileroptions="options"] #>
t4 参数指令
<#@ parameter type="full.typename" name="parametername" #>
t4 输出指令
<#@ output extension=".filenameextension" [encoding="encoding"] #>
t4 程序集指令
<#@ assembly name="[assembly strong name|assembly file name]" #>
$(solutiondir):当前项目所在解决方案目录
$(projectdir):当前项目所在目录
$(targetpath):当前项目编译输出文件绝对路径
$(targetdir):当前项目编译输出目录
t4 导入指令
<#@ import namespace="namespace" #>
t4 包含指令
<#@ include file="filepath" #>
4.2.2、文本块(text block)
文本块就是直接原样输出的静态文本,不需要添加任何的标签。
4.2.3、代码语句块(statement block)
代码语句块通过<# statement #>的形式表示,中间是一段通过相应编程语言编写的程序调用,我们可以通过代码语句快控制文本转化的流程。
4.2.4、表达式块(expression block)
表达式块以<#=expression #>的形式表示,通过它可以动态地解析字符串表达式内嵌到输出的文本中。
4.2.5、类特性块(class feature block)
如果文本转化需要一些比较复杂的逻辑,代码可能需要写在一个单独的辅助方法中,甚至是定义一些单独的类。类特性块的表现形式为<#+ featurecode #>。
五、t4模板生成数据库实体类
5.1、添加一个t4code文件夹,并在下面新建两个文本模板,需要注意的是,这里不使用默认的.tt扩展名,而是.ttinclude,将它们作为包含文件使用。
<#@ assembly name="system.core" #> <#@ assembly name="system.data" #> <#@ assembly name="$(projectdir)\lib\mysql.data.dll" #> <#@ import namespace="system.linq" #> <#@ import namespace="system.text" #> <#@ import namespace="system.collections.generic" #> <#@ import namespace="system.data"#> <#@ import namespace="system.data.sqlclient"#> <#@ import namespace="mysql.data.mysqlclient"#> <#+ #region t4code /// <summary> /// 数据库架构接口 /// </summary> public interface idbschema : idisposable { list<string> gettablelist(); table gettablemetadata(string tablename); } /// <summary> /// 数据库架构工厂 /// </summary> public class dbschemafactory { static readonly string databasetype = "sqlserver"; public static idbschema getdbschema() { idbschema dbschema; switch (databasetype) { case "sqlserver": { dbschema =new sqlserverschema(); break; } case "mysql": { dbschema = new mysqlschema(); break; } default: { throw new argumentexception("the input argument of databasetype is invalid."); } } return dbschema; } } /// <summary> /// sqlserver /// </summary> public class sqlserverschema : idbschema { public string connectionstring = "server=.;database=cfdev;uid=sa;pwd=********;"; public sqlconnection conn; public sqlserverschema() { conn = new sqlconnection(connectionstring); conn.open(); } public list<string> gettablelist() { datatable dt = conn.getschema("tables"); list<string> list = new list<string>(); foreach (datarow row in dt.rows) { list.add(row["table_name"].tostring()); } return list; } public table gettablemetadata(string tablename) { string commandtext = string.format("select * from {0}", tablename); ; sqlcommand cmd = new sqlcommand(commandtext, conn); sqldataadapter da = new sqldataadapter(cmd); dataset ds = new dataset(); da.fillschema(ds, schematype.mapped, tablename); table table = new table(ds.tables[0]); return table; } public void dispose() { if (conn != null) { conn.close(); } } } /// <summary> /// mysql /// </summary> public class mysqlschema : idbschema { public string connectionstring = "server=localhost;port=3306;database=projectdata;uid=root;pwd=;"; public mysqlconnection conn; public mysqlschema() { conn = new mysqlconnection(connectionstring); conn.open(); } public list<string> gettablelist() { datatable dt = conn.getschema("tables"); list<string> list = new list<string>(); foreach (datarow row in dt.rows) { list.add(row["table_name"].tostring()); } return list; } public table gettablemetadata(string tablename) { string commandtext = string.format("select * from {0}", tablename); ; mysqlcommand cmd = new mysqlcommand(commandtext, conn); mysqldataadapter da = new mysqldataadapter(cmd); dataset ds = new dataset(); da.fillschema(ds, schematype.mapped, tablename); table table = new table(ds.tables[0]); return table; } public void dispose() { if (conn != null) { conn.close(); } } } /// <summary> /// 数据表 /// </summary> public class table { public list<column> pks; public list<column> columns; public string datatypes; public table(datatable dt) { pks = getpklist(dt); columns = getcolumnlist(dt); datatypes = getdatatypelist(symbol.normal); } public list<column> getpklist(datatable dt) { list<column> list = new list<column>(); column column = null; if (dt.primarykey.length > 0) { list = new list<column>(); foreach (datacolumn dc in dt.primarykey) { column = new column(dc); list.add(column); } } return list; } private list<column> getcolumnlist(datatable dt) { list<column> list = new list<column>(); column column = null; foreach (datacolumn dc in dt.columns) { column = new column(dc); list.add(column); } return list; } private string getdatatypelist(symbol symbol) { list<string> list = new list<string>(); foreach (column c in columns) { if (symbol == symbol.normal) list.add(string.format("{0} {1}", c.datatype, c.uppercolumnname)); else if (symbol == symbol.underline) list.add(string.format("{0} _{1}", c.datatype, c.uppercolumnname)); } return string.join(",", list.toarray()); } } /// <summary> /// 数据列 /// </summary> public class column { datacolumn columnbase; public column(datacolumn _columnbase) { columnbase = _columnbase; } public string columnname { get { return columnbase.columnname; } } public string datatype { get { string result = string.empty; if (columnbase.datatype.name == "guid")//for mysql,因为对于mysql如果是char(36),类型自动为guid。 result = "string"; else if (columnbase.datatype.name == "string") result = "string"; else if (columnbase.datatype.name == "int32") result = "int"; else result = columnbase.datatype.name; return result; } } public string maxlength { get { return columnbase.maxlength.tostring(); } } public bool allowdbnull { get { return columnbase.allowdbnull; } } public string uppercolumnname { get { return string.format("{0}{1}", columnname[0].tostring().toupper(), columnname.substring(1)); } } public string lowercolumnname { get { return string.format("{0}{1}", columnname[0].tostring().tolower(), columnname.substring(1)); } } } /// <summary> /// 帮助类 /// </summary> public class generatorhelper { public static readonly string stringtype = "string"; public static readonly string datetimetype = "datetime"; public static string getquestionmarkbytype(string typename) { string result = typename; if (typename == datetimetype) { result += "?"; } return result; } } /// <summary> /// 符号枚举 /// </summary> public enum symbol { normal = 1, underline = 2 } #endregion #>
dbschema.ttinclude主要实现了数据库工厂的功能。注:请将数据库连接字符串改成您自己的。
<#@ assembly name="system.core" #> <#@ assembly name="system.data" #> <#@ assembly name="envdte" #> <#@ import namespace="system.linq" #> <#@ import namespace="system.text" #> <#@ import namespace="system.collections.generic" #> <#@ import namespace="system.data"#> <#@ import namespace="system.io"#> <#@ import namespace="microsoft.visualstudio.texttemplating"#> <#+ // t4 template block manager for handling multiple file outputs more easily. // copyright (c) microsoft corporation.all rights reserved. // this source code is made available under the terms of the microsoft public license (ms-pl) // manager class records the various blocks so it can split them up class manager { public struct block { public string name; public int start, length; } public list<block> blocks = new list<block>(); public block currentblock; public block footerblock = new block(); public block headerblock = new block(); public itexttemplatingenginehost host; public managementstrategy strategy; public stringbuilder template; public string outputpath { get; set; } public manager(itexttemplatingenginehost host, stringbuilder template, bool commonheader) { this.host = host; this.template = template; outputpath = string.empty; strategy = managementstrategy.create(host); } public void startblock(string name) { currentblock = new block { name = name, start = template.length }; } public void startfooter() { footerblock.start = template.length; } public void endfooter() { footerblock.length = template.length - footerblock.start; } public void startheader() { headerblock.start = template.length; } public void endheader() { headerblock.length = template.length - headerblock.start; } public void endblock() { currentblock.length = template.length - currentblock.start; blocks.add(currentblock); } public void process(bool split) { string header = template.tostring(headerblock.start, headerblock.length); string footer = template.tostring(footerblock.start, footerblock.length); blocks.reverse(); foreach(block block in blocks) { string filename = path.combine(outputpath, block.name); if (split) { string content = header + template.tostring(block.start, block.length) + footer; strategy.createfile(filename, content); template.remove(block.start, block.length); } else { strategy.deletefile(filename); } } } } class managementstrategy { internal static managementstrategy create(itexttemplatingenginehost host) { return (host is iserviceprovider) ? new vsmanagementstrategy(host) : new managementstrategy(host); } internal managementstrategy(itexttemplatingenginehost host) { } internal virtual void createfile(string filename, string content) { file.writealltext(filename, content); } internal virtual void deletefile(string filename) { if (file.exists(filename)) file.delete(filename); } } class vsmanagementstrategy : managementstrategy { private envdte.projectitem templateprojectitem; internal vsmanagementstrategy(itexttemplatingenginehost host) : base(host) { iserviceprovider hostserviceprovider = (iserviceprovider)host; if (hostserviceprovider == null) throw new argumentnullexception("could not obtain hostserviceprovider"); envdte.dte dte = (envdte.dte)hostserviceprovider.getservice(typeof(envdte.dte)); if (dte == null) throw new argumentnullexception("could not obtain dte from host"); templateprojectitem = dte.solution.findprojectitem(host.templatefile); } internal override void createfile(string filename, string content) { base.createfile(filename, content); ((eventhandler)delegate { templateprojectitem.projectitems.addfromfile(filename); }).begininvoke(null, null, null, null); } internal override void deletefile(string filename) { ((eventhandler)delegate { findanddeletefile(filename); }).begininvoke(null, null, null, null); } private void findanddeletefile(string filename) { foreach(envdte.projectitem projectitem in templateprojectitem.projectitems) { if (projectitem.get_filenames(0) == filename) { projectitem.delete(); return; } } } } #>
multidocument.ttinclude主要实现了多文档的功能。
5.2、添加一个multmodelauto.tt文本模板,代码如下:
<#@ template debug="true" hostspecific="true" language="c#" #> <#@ assembly name="system.core" #> <#@ import namespace="system.linq" #> <#@ import namespace="system.text" #> <#@ import namespace="system.collections.generic" #> <#@ output extension=".cs" #> <#@ include file="t4code/dbschema.ttinclude"#> <#@ include file="t4code/multidocument.ttinclude"#> <# var manager = new manager(host, generationenvironment, true) { outputpath = path.getdirectoryname(host.templatefile)}; #> <# //system.diagnostics.debugger.launch();//调试 var dbschema = dbschemafactory.getdbschema(); list<string> tablelist = dbschema.gettablelist(); foreach(string tablename in tablelist) { manager.startblock(tablename+".cs"); table table = dbschema.gettablemetadata(tablename); #> //------------------------------------------------------------------------------- // 此代码由t4模板multmodelauto自动生成 // 生成时间 <#=datetime.now.tostring("yyyy-mm-dd hh:mm:ss")#> // 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。 //------------------------------------------------------------------------------- using system; using system.collections.generic; using system.text; namespace project.model { [serializable] public class <#=tablename#> { #region constructor public <#=tablename#>() { } public <#=tablename#>(<#=table.datatypes#>) { <# foreach(column c in table.columns) { #> this.<#=c.uppercolumnname#> = <#=c.uppercolumnname#>; <# } #> } #endregion #region attributes <# foreach(column c in table.columns) { #> public <#=generatorhelper.getquestionmarkbytype(c.datatype)#> <#=c.uppercolumnname#> {get; set;} <# } #> #endregion #region validator public list<string> errorlist = new list<string>(); private bool validator() { bool validatorresult = true; <# foreach(column c in table.columns) { if (!c.allowdbnull) { if(c.datatype == generatorhelper.stringtype) { #> if (string.isnullorempty(<#=c.uppercolumnname#>)) { validatorresult = false; errorlist.add("the <#=c.uppercolumnname#> should not be empty."); } <# } if(c.datatype == generatorhelper.datetimetype) { #> if (<#=c.uppercolumnname#> == null) { validatorresult = false; errorlist.add("the <#=c.uppercolumnname#> should not be empty."); } <# } } if (c.datatype == generatorhelper.stringtype) { #> if (<#=c.uppercolumnname#> != null && <#=c.uppercolumnname#>.length > <#=c.maxlength#>) { validatorresult = false; errorlist.add("the length of <#=c.uppercolumnname#> should not be greater then <#=c.maxlength#>."); } <# } } #> return validatorresult; } #endregion } } <# manager.endblock(); } dbschema.dispose(); manager.process(true); #>
代码保存后,可以看到此文件下面已按数据表生成了多个实体类文件:
参考自: