很早就看过T4模板的介绍,是可以自定义规则来生成文件的,不过当时没时间研究就跳过了,继续使用动软生成代码。
现在终于抽时间学下T4模板,多亏大神们的无私分享,使我很快就用上T4模板了,灰常灰常感谢大大们~~
在这里我也总结下使用情况,有说的不好的欢迎指出~~
T4文件后缀主要有:tt和ttinclude,tt文件是模板文件,每次保存VS都会提示是否执行代码;ttinclude文件是tt的辅助文件,保存不会提示执行代码。
需要生成代码,一般都是映射数据库了,当然也可以用来生成其他比较统一格式的文件,全看您的模板怎么写,这里我是用来生成映射数据库表的实体类。
(如果想让T4模板代码高亮,有提示,可以下载安装T4模板插件)
这是我的T4模板文件结构
1.DBSchema.ttinclude
首先,我是找到一个DBSchema.ttinclude文件,此文件是用来访问数据库,从数据库读出表的信息,代码如下,我根据我自己的情况改动了一些
(由于找资料时没记住出处,忘记是copy哪个大神的了,大神请见谅^_^|||)
public classDBSchemaFactory
{public static IDBSchema GetDBSchema(stringdbType)
{
IDBSchema dbSchema;string connectionString =String.Empty;switch(dbType)
{case "SqlServer":
connectionString= "Data Source=.;Initial Catelog=dbName;Persist Security Info=True;User ID=sa;Password=sa;";
dbSchema= newSqlServerSchema(connectionString);break;case "MySql":
connectionString= "Server=localhost;Port=3306;Database=dbName;Uid=root;Pwd=pwd;charset=utf8;";
dbSchema= newMySqlSchema(connectionString);break;default:throw new ArgumentException("The input argument of DatabaseType is invalid!");
}returndbSchema;
}public interfaceIDBSchema
{
List
}public classSqlServerSchema : IDBSchema
{publicSqlConnection conn;public SqlServerSchema(stringconnString)
{
conn= newSqlConnection(connString);
}public List
{
List
conn.Open();var cmd = string.Format(@"SELECT tab.name AS TABLE_NAME, col.name AS COLUMN_NAME,
col.is_identity, per.value AS COLUMN_COMMENT, t.name AS DATA_TYPE
FROM {0}.sys.columns col INNER JOIN {0}.sys.tables tab
ON col.object_id = tab.object_id LEFT JOIN {0}.sys.extended_properties per
ON col.column_id = per.minor_id AND per.major_id = tab.object_id
INNER JOIN {0}.SYS.types t ON col.user_type_id = t.user_type_id", dbName);
SqlCommand command= newSqlCommand(cmd, conn);using(SqlDataReader reader =command.ExecuteReader())
{while(reader.Read())
{string db =dbName,
table= reader["TABLE_NAME"].ToString(),
column= reader["COLUMN_NAME"].ToString(),
type= reader["DATA_TYPE"].ToString(),
comment= reader["COLUMN_COMMENT"].ToString(),
pk= reader["is_identity"].ToString();
Table entity= list.FirstOrDefault(x => x.TableName ==table);if (entity == null)
{
entity= newTable(table);
entity.Columns.Add(newColumn
{
Name=column,
Type=GetCLRType(type),
Comment=comment,
IsPK= pk == "1" ? true : false});
list.Add(entity);
}else{
entity.Columns.Add(newColumn
{
Name=column,
Type=GetCLRType(type),
Comment=comment,
IsPK= pk == "1" ? true : false});
}
}
}
}finally{
conn.Close();
}returnlist;
}
}public classMySqlSchema : IDBSchema
{publicMySqlConnection conn;public MySqlSchema(stringconnString)
{
conn= newMySqlConnection(connString);
}public List
{
List
conn.Open();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`
,`information_schema`.`COLUMNS`.`COLUMN_KEY`
FROM `information_schema`.`COLUMNS`
WHERE `information_schema`.`COLUMNS`.`TABLE_SCHEMA` = '{0}'", dbName);
MySqlCommand command= newMySqlCommand(cmd, conn);using(MySqlDataReader reader =command.ExecuteReader())
{while(reader.Read())
{string db = reader["TABLE_SCHEMA"].ToString(),
table= reader["TABLE_NAME"].ToString(),
column= reader["COLUMN_NAME"].ToString(),
type= reader["DATA_TYPE"].ToString(),
comment= reader["COLUMN_COMMENT"].ToString(),
pk= reader["COLUMN_KEY"].ToString();
Table entity= list.FirstOrDefault(x => x.TableName ==table);if (entity == null)
{
entity= newTable(table);
entity.Columns.Add(newColumn
{
Name=column,
Type=GetCLRType(type),
Comment=comment,
IsPK= pk == "PRI" ? true : false});
list.Add(entity);
}else{
entity.Columns.Add(newColumn
{
Name=column,
Type=GetCLRType(type),
Comment=comment,
IsPK= pk == "PRI" ? true : false});
}
}
}
}finally{
conn.Close();
}returnlist;
}
}public static string GetCLRType(stringdbType)
{switch(dbType)
{case "tinyint":case "smallint":case "mediumint":case "int":case "integer":return "int?";case "double":return "double?";case "float":return "float?";case "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:returndbType;
}
}
}public classTable
{publicTable()
{this.Columns = new List();
}public Table(stringname)
:this()
{this.TableName =name;
}public string TableName { get; set; }public List Columns { get; set; }
}public classColumn
{//字段名
public string Name { get; set; }//类型
public string Type { get; set; }//备注
public string Comment { get; set; }//是否主键
public bool IsPK { get; set; }
}
#>
DBSchema.ttinclude
原先copy的只有针对SQL Server的,不过我需要MySql,所以上网找了个连接MySql的加上了。
--
这里的$(ProjectDir)当前项目所在目录路径,还可以用其他:
$(SolutionDir):当前项目所在解决方案目录
$(ProjectDir):当前项目所在目录
$(TargetPath):当前项目编译输出文件绝对路径
$(TargetDir):当前项目编译输出目录
-- public bool IsPK { get; set; } //这个是我用来记录该列是否主键,我生成实体类时需要用到,不需要可以去掉
SQL Server是通过sys.columns.is_identity获取,MySql是通过information_schema.COLUMNS.COLUMN_KEY获取。
OK,其他没怎么改动过了。
2.MultDocument.ttinclude
数据库连接有了,不过我根据大神的分享,生成实体类,发现每次只能生成一个表实体类,这完全不合理嘛,于是又上网找了下批量生成的资料。。。
很快就找到MultDocument.ttinclude文件了,据介绍,是外国一个大神分享的,这个文件没什么改的,我就直接用了一 一+
//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 u
classManager
{public structBlock
{publicString Name;public intStart, Length;
}public List blocks = new List();publicBlock currentBlock;public Block footerBlock = newBlock();public Block headerBlock = newBlock();publicITextTemplatingEngineHost host;publicManagementStrategy strategy;publicStringBuilder template;public String OutputPath { get; set; }public Manager(ITextTemplatingEngineHost host, StringBuilder template, boolcommonHeader)
{this.host =host;this.template =template;
OutputPath=String.Empty;
strategy=ManagementStrategy.Create(host);
}public voidStartBlock(String name)
{
currentBlock= new Block { Name = name, Start =template.Length };
}public voidStartFooter()
{
footerBlock.Start=template.Length;
}public voidEndFooter()
{
footerBlock.Length= template.Length -footerBlock.Start;
}public voidStartHeader()
{
headerBlock.Start=template.Length;
}public voidEndHeader()
{
headerBlock.Length= template.Length -headerBlock.Start;
}public voidEndBlock()
{
currentBlock.Length= template.Length -currentBlock.Start;
blocks.Add(currentBlock);
}public void Process(boolsplit)
{
String header=template.ToString(headerBlock.Start, headerBlock.Length);
String footer=template.ToString(footerBlock.Start, footerBlock.Length);
blocks.Reverse();foreach (Block block inblocks)
{
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);
}
}
}
}classManagementStrategy
{internal staticManagementStrategy Create(ITextTemplatingEngineHost host)
{return (host is IServiceProvider) ? new VSManagementStrategy(host) : newManagementStrategy(host);
}internalManagementStrategy(ITextTemplatingEngineHost host) { }internal virtual voidCreateFile(String fileName, String content)
{
File.WriteAllText(fileName, content);
}internal virtual void DeleteFile(stringfileName)
{if(File.Exists(fileName))
File.Delete(fileName);
}
}classVSManagementStrategy: ManagementStrategy
{privateEnvDTE.ProjectItem templateProjectItem;internalVSManagementStrategy(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 voidCreateFile(String fileName, String content)
{base.CreateFile(fileName, content);
((EventHandler)delegate{
templateProjectItem.ProjectItems.AddFromFile(fileName);
}).BeginInvoke(null, null, null, null);
}internal override voidDeleteFile(String fileName)
{
((EventHandler)delegate{
FindAndDeleteFile(fileName);
}).BeginInvoke(null, null, null, null);
}private voidFindAndDeleteFile(String fileName)
{foreach (EnvDTE.ProjectItem projectItem intemplateProjectItem.ProjectItems)
{if (projectItem.get_FileNames(0) ==fileName)
{
projectItem.Delete();return;
}
}
}
}
#>
MultDocument.ttinclude
各位看官,直接copy存为ttinclude就可以了。
3.CommonAttr.ttinclude
准备工作差不多了,不过在写tt模板之前,我先说下我抽出来的公用参数/方法。
public classAttributes
{//文件版权信息
public static string Copyright = DateTime.Now.Year + "RoryLiu All Rights Reserved";public static Version Version =Environment.Version;public static string Author = "auto generated by T4";public static string DbType = "MySql";//数据库类型
public static string DbName = "dbName";//数据库名
public static string ProName = "Namespace";//命名空间
public static bool IsGo = true;//是否执行
public static string TableNames = "*";//全部用"*",部分表用",表名,表名,..."
}//得到属性的Pascial风格名称,比如my_table => MyTable
public string GetPropertyPascialName(stringsource)
{string[] s = source.Split('_');for(int i = 0; i < s.Length; i++)
{string s1 = s[i].Substring(0, 1).ToUpper();string s2 = s[i].Substring(1);
s[i]=String.Concat(s1,s2);
}returnString.Concat(s);
}
#>
CommonAttr.ttinclude
将这些参数抽出来主要是为了方便生成Model、DAL、BLL等模板,不一定要用这个文件。
4.ModelTemp.tt
准备工作都做完了,终于到模板了,各位看官等急了吧,先给大家看下生成实体类的模板:(ModelTemp.tt其实也是上网找的,然后自己再修改( ‵▽′)ψ)
if (!Attributes.IsGo) return "";//命名空间
var nsName = Attributes.ProName + ".Models";//实例化生成模板
var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath =Path.GetDirectoryName(Host.TemplateFile)};//获取连接的数据库
var dbSchema =DBSchemaFactory.GetDBSchema(Attributes.DbType);//获取数据库下的所有表
var entities =dbSchema.GetTables(Attributes.DbName);//实体名
var entityName = "";foreach(Table entity inentities)
{//只生成指定的表实体
if (!Attributes.TableNames.Equals("*") && !Attributes.TableNames.Contains("," + entity.TableName + ","))continue;//生成实体名
entityName =GetPropertyPascialName(entity.TableName);
manager.StartBlock(entityName+ ".cs");
Column pkCol= entity.Columns.Find(c =>c.IsPK);foreach (var col inentity.Columns)
{if(col.IsPK)
{
pkCol=col;break;
}
}
#>
//-----------------------------------------------------------------------* Copyright (C) //* version : //* author : //* FileName: .cs//* history : Created by T4 -----------------------------------------------------------------------
usingSystem;usingSystem.ComponentModel.DataAnnotations;namespace {///
/// Entity Model///
[Serializable]
[Table("")]public partial class {public () { }
{
#>
///
///
///
{
#>[Key]
}
#>
public { get; set; }
}
#>}
}
manager.EndBlock();
}
manager.Process(true);
#>
ModelTemp.tt
头部有三个,相信大家都知道是神马了,没错,就是引用前面准备的三个ttinclude文件。
-- Attributes.IsGo,其实这个不用也没什么影响,我是因为不想每次保存都弹出提示是否执行才加的,我设置成不再提示,然后用IsGo来判断是否执行
-- 实体名我没用数据库表名,而是通过GetPropertyPascialName()将表名变成Pascial风格,真正的表名我是写在自定义特性Table里
-- 循环所有表时,我是通过Attributes.TableNames来确定哪些表要生成文件,这样后期有改动某个表时不需要全部生成
-- 为了给主键属性加上特性[Key],我是通过IsPK来判断
模板基本就改了这些。
5.Table.cs
Table类是自定义特性,继承了System.Attribute,代码如下:
///
///映射数据库表对象///
[AttributeUsageAttribute(AttributeTargets.Class, Inherited = false, AllowMultiple = false), Serializable]public classTable : Attribute
{///
///表名称///
public stringTableName;public Table(stringtblName)
{
TableName=tblName;
}
}
Table.cs
我是直接放在根目录下,当然如果有多个自定义特性时,还是建议建个文件夹。
好了,现在只要把CommonAttr里的IsGo改为true,DBSchema里的connectionString改成您本地的mysql就可以保存ModelTemp来生成了。