总体介绍
参考文档:
https://www.cnblogs.com/laozhang-is-phi/p/9757999.html
只想用 ADO.NET 搭建多层框架,动软代码生成器是一个不错的选择。
T4 (Text Template Transformation Toolkit) 是微软官方在 VisualStudio 2008+ 中开始使用的代码生成引擎。在 Visual Studio 中,“T4 文本模板”是由一些文本块和控制逻辑组成的混合模板,它可以生成文本文件。 在 Visual C# 或 Visual Basic 中,控制逻辑编写为程序代码的片段。生成的文件可以是任何类型的文本,例如网页、资源文件或任何语言的程序源代码。现在的VS中只要与代码生成相关的场景基本上都能找T4的身影,比如MVC的视图模板,Entity Framwork的DataContext模板等等。
这里就不具体讲解 T4 语法了,大家可以自行学习,其实很简单,主要还是 C# 代码,下边你看过之后就能懂了,咱们首先先实现之前留下的一个伏笔 —— 将我们的数据库表利用T4 模板生成实体类,也就是 DbFirst。
1 首先在我们的项目中,新建一个类库 Xwy.Core.FrameWorkT4Mysql
2 在该类库下,新建文件夹 Xwy.Core.FrameWorkT4Mysql.Entity,用于单独存放我们的模板以及生成的实体类文件
3 ModelAuto.ttinclude 文本文件代码
//引入命名空间 <#@ assembly name="System.Core"#> <#@ assembly name="EnvDTE"#> <#@ import namespace="System.Collections.Generic"#> <#@ import namespace="System.IO"#> <#@ import namespace="System.Text"#> <#@ import namespace="Microsoft.VisualStudio.TextTemplating"#> <#+ //定义管理者 manager 实体类 class Manager { //定义一个 block 块,主要是应用在批量生产中 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; } //构造函数,包含 host主机,模板,输出路径,创建管理策略 public Manager(ITextTemplatingEngineHost host, StringBuilder template, bool commonHeader) { this.host = host; this.template = template; OutputPath = String.Empty; strategy = ManagementStrategy.Create(host); } //开辟一个 block 块 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); } //定义进程,用来将所有的 blocks 块执行出来 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; } } } }#>
4 MySqlHelper.ttinclude文本文件
<#+ public class EntityHelper { public static List<Entity> GetEntities(string connectionString, List<string> databases) { var list = new List<Entity>(); var conn = new MySqlConnection(connectionString); try { conn.Open(); var dbs = string.Join("','", databases.ToArray()); var cmd = string.Format(@"SELECT `information_schema`.`COLUMNS`.`TABLE_SCHEMA` ,`information_schema`.`COLUMNS`.`TABLE_NAME` ,`information_schema`.`COLUMNS`.`COLUMN_NAME` ,`information_schema`.`COLUMNS`.`DATA_TYPE` ,`information_schema`.`COLUMNS`.`COLUMN_COMMENT` FROM `information_schema`.`COLUMNS` WHERE `information_schema`.`COLUMNS`.`TABLE_SCHEMA` IN ('{0}') ", dbs); using (var reader = MySqlHelper.ExecuteReader(conn, cmd)) { while (reader.Read()) { var db = reader["TABLE_SCHEMA"].ToString(); var table = reader["TABLE_NAME"].ToString(); var column = reader["COLUMN_NAME"].ToString(); var type = reader["DATA_TYPE"].ToString(); var comment = reader["COLUMN_COMMENT"].ToString(); var entity = list.FirstOrDefault(x => x.EntityName == table); if(entity == null) { entity = new Entity(table); entity.Fields.Add(new Field { Name = column, Type = GetCLRType(type), Comment = comment }); list.Add(entity); } else { entity.Fields.Add(new Field { Name = column, Type = GetCLRType(type), Comment = comment }); } } } } finally { conn.Close(); } return list; } public static string GetCLRType(string dbType) { switch(dbType) { case "tinyint": case "smallint": case "mediumint": case "int": case "integer": return "int"; case "double": return "double"; case "float": return "float"; case "decimal": return "decimal"; case "numeric": case "real": return "decimal"; case "bit": return "bool"; case "date": case "time": case "year": case "datetime": case "timestamp": return "DateTime"; case "tinyblob": case "blob": case "mediumblob": case "longblog": case "binary": case "varbinary": return "byte[]"; case "char": case "varchar": case "tinytext": case "text": case "mediumtext": case "longtext": return "string"; case "point": case "linestring": case "polygon": case "geometry": case "multipoint": case "multilinestring": case "multipolygon": case "geometrycollection": case "enum": case "set": default: return dbType; } } } public class Entity { public Entity() { this.Fields = new List<Field>(); } public Entity(string name) : this() { this.EntityName = name; } public string EntityName { get;set; } public List<Field> Fields { get;set; } public string PascalEntityName { get{ return CommonConver.ToPascalCase(this.EntityName); } } public string CamelEntityName { get{ return CommonConver.ToCamelCase(this.EntityName); } } } public class Field { public string Name { get;set; } public string Type { get;set; } public string Comment { get;set; } } public class CommonConver { public static string ToPascalCase(string tableName) { string upperTableName = tableName.Substring(0, 1).ToUpper() + tableName.Substring(1, tableName.Length - 1); return upperTableName; } public static string ToCamelCase(string tableName) { string lowerTableName = tableName.Substring(0, 1).ToLower() + tableName.Substring(1, tableName.Length - 1); return lowerTableName; } } class config { public static readonly string ConnectionString = "Database=openauth;Data Source=127.0.0.1;User Id=root;Password=;pooling=false;CharSet=utf8;port=3306"; public static readonly string DbDatabase = ""; public static readonly string TableName = ""; public static readonly string ModelNameSpace = "App.Entities"; public static readonly string IRepositoryNameSpace = "App.IRepository"; public static readonly string RepositoryNameSpace = "App.Repository"; public static readonly string IServicesNameSpace = "App.IServices"; public static readonly string ServicesNameSpace = "App.Services"; }#>
5 模板文件Entity.tt
<#@ template debug="false" hostspecific="True" language="C#" #> <#@ output extension=".cs" #> <#@ assembly name="System.Core.dll" #> <#@ assembly name="System.Data.dll" #> <#@ assembly name="System.Data.DataSetExtensions.dll" #> <#@ assembly name="System.Xml.dll" #> <#@ assembly name="C:Userschuanchuan.nugetpackagesmysql.data8.0.14lib et452MySql.Data.dll" #> <#@ import namespace="System" #> <#@ import namespace="System.Xml" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Data" #> <#@ import namespace="System.Data.SqlClient" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.IO" #> <#@ import namespace="MySql.Data.MySqlClient" #> <#@ include file="$(ProjectDir)MySqlHelper.ttinclude" #> <#@ include file="$(ProjectDir)ModelAuto.ttinclude" #> <# // 是否是WCF服务模型 //C:Usersyouga.nugetpackagesmysql.data8.0.14lib et452 bool serviceModel = false; // 数据库连接 var connectionString = @"Data Source=127.0.0.1;User Id=root;Password=;pooling=false;CharSet=utf8;port=3306"; // 需要解析的数据库 var database = new List<string> { "openauthdb" }; // 文件版权信息 var copyright = DateTime.Now.Year + " xxxx Enterprises All Rights Reserved"; var version = Environment.Version; var author = "auto generated by T4"; var OutputPath1 = Path.GetDirectoryName(Host.TemplateFile)+"\work"; if (!Directory.Exists(OutputPath1)) { Directory.CreateDirectory(OutputPath1); } var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath = OutputPath1 }; var entities = EntityHelper.GetEntities(connectionString, database); foreach(Entity entity in entities) { manager.StartBlock(entity.EntityName + ".cs"); #> //----------------------------------------------------------------------- // <copyright file=" <#= entity.EntityName #>.cs" company="xxxx Enterprises"> // * Copyright (C) <#= copyright #> // * version : <#= version #> // * author : <#= author #> // * FileName: <#= entity.EntityName #>.cs // * history : Created by T4 <#= DateTime.Now #> // </copyright> //----------------------------------------------------------------------- using System; <# if(serviceModel) { #> using System.Runtime.Serialization; <# } #> namespace <#=config.ModelNameSpace#> { /// <summary> /// <#= entity.EntityName #> Entity Model /// </summary> [Serializable] <# if(serviceModel) { #> [DataContract] <# } #> public class <#= entity.EntityName #> { <# for(int i = 0; i < entity.Fields.Count; i++) { if(i ==0){ #> /// <summary> /// <#= entity.Fields[i].Comment #> /// </summary> <# if(serviceModel) { #> [DataMember] <# } #> public <#= entity.Fields[i].Type #> <#= entity.Fields[i].Name #> { get; set; } <# } else{ #> /// <summary> /// <#= entity.Fields[i].Comment #> /// </summary> <# if(serviceModel) { #> [DataMember] <# } #> public <#= entity.Fields[i].Type #> <#= entity.Fields[i].Name #> { get; set; } <# } } #> } } <# manager.EndBlock(); } manager.Process(true); #>
注意事项:
1、<#@ assembly name="C:Usersyouga.nugetpackagesmysql.data8.0.14lib
et452MySql.Data.dll" #> 修改为你的引用;
2、MySqlHelper.ttinclude 修改ConnectionString变量;
3、App.Entities.tt 修改connectionString变量;
4、可以根据自己的需求修改T4模板;
5、生成的类名都是小写,可否转化为帕斯卡命名规范(待解决);
6、如果MySQL要生成大写的话,需要在my.ini中添加 lower_case_table_names=2,让mysql区分大小写,生成出来的就跟所设置的表名,字段名一样了,默认mysql在windows下不区分大小写。
<#@ template debug="false" hostspecific="True" language="C#" #><#@ output extension=".cs" #>
<#@ assembly name="System.Core.dll" #><#@ assembly name="System.Data.dll" #><#@ assembly name="System.Data.DataSetExtensions.dll" #><#@ assembly name="System.Xml.dll" #><#@ assembly name="C:Userschuanchuan.nugetpackagesmysql.data8.0.14lib
et452MySql.Data.dll" #><#@ import namespace="System" #><#@ import namespace="System.Xml" #><#@ import namespace="System.Linq" #><#@ import namespace="System.Data" #><#@ import namespace="System.Data.SqlClient" #><#@ import namespace="System.Collections.Generic" #><#@ import namespace="System.IO" #><#@ import namespace="MySql.Data.MySqlClient" #>
<#@ include file="$(ProjectDir)MySqlHelper.ttinclude" #><#@ include file="$(ProjectDir)ModelAuto.ttinclude"#>
<# // 是否是WCF服务模型//C:Usersyouga.nugetpackagesmysql.data8.0.14lib
et452 bool serviceModel = false; // 数据库连接 var connectionString = @"Data Source=127.0.0.1;User Id=root;Password=;pooling=false;CharSet=utf8;port=3306";
// 需要解析的数据库 var database = new List<string> { "openauthdb" };
// 文件版权信息 var copyright = DateTime.Now.Year + " xxxx Enterprises All Rights Reserved"; var version = Environment.Version; var author = "auto generated by T4";
var OutputPath1 = Path.GetDirectoryName(Host.TemplateFile)+"\work";if (!Directory.Exists(OutputPath1)){Directory.CreateDirectory(OutputPath1);}
var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath = OutputPath1 }; var entities = EntityHelper.GetEntities(connectionString, database);
foreach(Entity entity in entities) { manager.StartBlock(entity.EntityName + ".cs");#>//-----------------------------------------------------------------------// <copyright file=" <#= entity.EntityName #>.cs" company="xxxx Enterprises">// * Copyright (C) <#= copyright #>// * version : <#= version #>// * author : <#= author #>// * FileName: <#= entity.EntityName #>.cs// * history : Created by T4 <#= DateTime.Now #>// </copyright>//-----------------------------------------------------------------------using System;<# if(serviceModel) {#>using System.Runtime.Serialization;<# }#>
namespace <#=config.ModelNameSpace#>{ /// <summary> /// <#= entity.EntityName #> Entity Model /// </summary> [Serializable]<# if(serviceModel) {#> [DataContract]<# }#> public class <#= entity.EntityName #> {<# for(int i = 0; i < entity.Fields.Count; i++) { if(i ==0){#> /// <summary> /// <#= entity.Fields[i].Comment #> /// </summary><# if(serviceModel) {#> [DataMember]<# }#> public <#= entity.Fields[i].Type #> <#= entity.Fields[i].Name #> { get; set; }<# } else{#> /// <summary> /// <#= entity.Fields[i].Comment #> /// </summary><# if(serviceModel) {#> [DataMember]<# }#> public <#= entity.Fields[i].Type #> <#= entity.Fields[i].Name #> { get; set; }<# } }#> }}<# manager.EndBlock(); }
manager.Process(true);#>