首页 > temp > 简明python教程 >
-
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; } }