一、简介
开源代码生成器-Kalman Studio
https://github.com/loamen/Kalman.Studio
1、软件主要功能如下:
1、基于T4的代码生成工具,根据数据库元数据信息生成代码,支持多数据库,支持批量代码生成;
2、支持根据PowerDesigner物理模型文件来生成代码;
3、内置了一个简单的文本编辑器,支持代码高亮显示;
4、数据库元数据信息浏览工具;
5、数据库文档生成工具,支持输出word及pdf文件格式;
6、IIS日志解析器,小网站可以用用;
7、其他工具,字符串相关操作等。
二、编写T4模板:
1、VS自动根据模板自动生成代码
首先,打开Visual Studio,新建一个文本文件,将扩展名改为"tt"
这时Visual Studio会为Entity.tt生成一个对应的类文件Entity.cs。这是因为Visual Studio内置了一个代码生成工具“TextTemplatingFileGenerator”,会自动为每个模板文件生成对应的类文件,如果你在Entity.tt中编辑好模板,然后打开Entity.cs,TextTemplatingFileGenerator会自动解析模板,将代码输出到文件Entity.cs。
2、编写T4模板
我们这里不需要使用TextTemplatingFileGenerator生成代码,把属性面板,“自定义工具”里面的文字“TextTemplatingFileGenerator”删掉,对应的类文件会自动删除;然后可以开始编写模板了。
我这里先展示一个编写好的实体模板,然后再详细说明。
<#@ template language="C#v3.5" hostSpecific="true" debug="true" #> <#@ output extension=".cs" #> <# TableHost host = (TableHost)(Host); SOTable table = host.Table; List<SOColumn> list = host.ColumnList; string columnPrefix = host.GetString("ColumnPrefix"); int prefixLevel = host.GetInt32("PrefixLevel"); string nameSpace = host.GetString("NameSpace"); string className = host.GetString("ClassName"); if(string.IsNullOrEmpty(nameSpace))nameSpace = "Entity"; if(string.IsNullOrEmpty(className))className = table.Name; #> using System; using System.Collections.Generic; using System.Text; using System.Data; namespace <#= nameSpace #> { /// <summary> /// <#= table.Comment == "" ? table.Name : table.Comment #> /// </summary> [Serializable] public partial class <#= className #> { <# foreach (SOColumn c in list) { #>/// <summary> /// <#= c.Comment == "" ? c.Name : c.Comment #> /// </summary> public <#= TypeUtil.DbType2TypeString(c.DataType) #> <#= StringUtil.RemovePrefix(c.Name, columnPrefix, prefixLevel).Replace(" ", "") #> { get; set; } <# } #> } }
3、T4模板语法
- <#@ template language="C#v3.5" hostSpecific="true" debug="true" #>:可以指定模板使用的语言,hostSpecific="true"表示是否使用特定的host(Kalman Studio里面使用的是TableHost对象,必须实现接口ITextTemplatingEngineHost)。
- <#@ output extension=".cs" #> : 指定生成文件的扩展名。
- <#@ assembly name="System.Data" #>:添加程序集引用,如果要使用第三方程序集,那么最好在项目中添加引用,或者加入到GAC。
- <#@ import namespace="System.Data" #>:导入要使用的命名空间,注意:这里的命名空间必须要在前面指定的程序集里面找得到的,比如我指定命名空间"System.Data","System.Data.Common",这些在程序集System.Data中都有的。
- <#@ include file="test.tt" #> 导入模板,类似Html的include用法
- <# #>:定义代码块
- <#= #>:定义表达式
- <#+ #>:定义变量
三、Kalman Studio改进
1、自定义架构对象SchemaObject下的类。
2、自定义T4模板的引擎主机类:T4TemplatingEngineHost
3、自定义SqlServerSchema
更改Kalman项目的SqlServerSchema文件,增加Computed是否是计算字段的判断。

public override List<SOColumn> GetTableColumnList(SOTable table) { string cmdText = string.Format(@"use [{2}];select colm.name column_name, object_definition(colm.default_object_id) as column_def, systype.name type_name, colm.is_identity, colm.is_nullable , cast(colm.max_length as int) length, cast(colm.precision as int) precision, cast(colm.scale as int) scale, colm.is_computed from sys.columns colm inner join sys.types systype on colm.system_type_id = systype.system_type_id and colm.user_type_id = systype.user_type_id where colm.object_id = object_id('{0}') order by colm.column_id;", table.Name, table.Owner, table.Database.Name); List<SOColumn> columnList = new List<SOColumn>(); List<string> pkList = GetPrimaryKeys(table); DataTable dt = this.DbProvider.ExecuteDataSet(System.Data.CommandType.Text, cmdText).Tables[0]; foreach (DataRow row in dt.Rows) { SOColumn column = new SOColumn { Parent = table, Name = row["column_name"].ToString(), DefaultValue = row["column_def"].ToString(), Nullable = row["is_nullable"].ToString()== "True", NativeType = row["type_name"].ToString(), Identify = row["is_identity"].ToString() == "True", Computed = row["is_computed"].ToString() == "True", //ForeignKey Length = ConvertUtil.ToInt32(row["length"], -1), Precision = ConvertUtil.ToInt32(row["precision"], -1), Scale = ConvertUtil.ToInt32(row["scale"], –1), }; column.PrimaryKey = pkList.Contains(column.Name); column.DataType = this.GetDbType(column.NativeType); column.Comment = GetColumnComment(column); columnList.Add(column); } return columnList; }
4、常用的几个自定义的T4模板
1、生成实体:model.tt
<#@ template language="C#v3.5" hostSpecific="true" debug="true" #> <#@ output extension=".cs" #> <# TableHost host = (TableHost)(Host); SOTable table = host.Table; List<SOColumn> list = host.ColumnList; string nameSpace = host.GetString("NameSpace"); string className = host.GetString("ClassName"); if(string.IsNullOrEmpty(nameSpace))nameSpace = "Entity"; if(string.IsNullOrEmpty(className))className = table.Name; #> using System; using System.Collections.Generic; using System.Text; using System.Data; namespace <#= nameSpace #> { /// <summary> /// <#= table.Comment == "" ? table.Name : table.Comment.Replace(" "," ") #> /// </summary> [Serializable] public partial class <#= className+"Entity" #> { <# foreach (SOColumn c in list) {#> /// <summary> /// <#= c.Comment == "" ? c.Name : c.Comment.Replace(" "," ") #> /// </summary> public <#= TypeUtil.DbType2TypeString(c.DataType) #><# if(c.Nullable) { #><#if(TypeUtil.DbType2Type(c.DataType).IsValueType){ #>?<# }#><# }#> <#= c.Name #>{get;set;}; <# } #> } }
2、生成数据访问层:DAL.tt
<#@ template language="C#v3.5" hostSpecific="true" debug="true" #><#@ output extension=".cs" #> <# TableHost host = (TableHost)(Host); SOTable table = host.Table; List<SOColumn> list = host.ColumnList; string nameSpace = host.GetString("NameSpace"); string className = host.GetString("ClassName"); if(string.IsNullOrEmpty(nameSpace))nameSpace = "DAL"; if(string.IsNullOrEmpty(className))className = table.Name; List<SOColumn> listPK = new List<SOColumn>(); List<SOColumn> listOtherCol =new List<SOColumn>(); List<string> listAdd =new List<string>(); List<string> listUpdate =new List<string>(); foreach (SOColumn c in list) { if(c.PrimaryKey) { listPK.Add(c); } if((c.PrimaryKey&&!c.Identify) ||!c.PrimaryKey ) { if( !c.Computed) { listOtherCol.Add(c); listAdd.Add("+ "," + SqlNull(entity." + c.Name + ")"); if(!c.PrimaryKey) listUpdate.Add("+ ","+ c.Name + "="+ SqlNull(entity." + c.Name + ")"); } }} //colAll System.Text.StringBuilder sbcolAllStr = new StringBuilder(); List<string> list1= list.ConvertAll(p=>p.Name); for (int i = 0; i < list1.Count; i++) { sbcolAllStr.Append("t."+list[i] + ","); if ((i + 1) % 5 == 0 && (i + 1)!=list1.Count ) sbcolAllStr.Append(" "); } string colAllStr = sbcolAllStr.ToString().TrimEnd(','); //colOtherStr System.Text.StringBuilder sbcolOtherStr = new StringBuilder(); List<string> list2= listOtherCol.ConvertAll(p=>p.Name); for (int i = 0; i < list2.Count; i++) { if(list2[i].IndexOf("UpdateUser")>-1||list2[i].IndexOf("UpdateTime")>-1) continue; sbcolOtherStr.Append(list2[i] + ","); if ((i + 1) % 5 == 0 && (i + 1)!=list2.Count) sbcolOtherStr.Append(" "); } string colOtherStr = sbcolOtherStr.ToString().TrimEnd(','); //AddStr System.Text.StringBuilder sblistAddStr = new StringBuilder(); for (int i = 0; i < listAdd.Count; i++) { if(listAdd[i].IndexOf("UpdateUser")>-1||listAdd[i].IndexOf("UpdateTime")>-1) continue; sblistAddStr.Append(listAdd[i] + ""); if ((i + 1) % 5 == 0 && (i + 1)!=listAdd.Count) sblistAddStr.Append(" "); } string listAddStr = System.Text.RegularExpressions.Regex.Replace(sblistAddStr.ToString(), @"^+s*"",""s", ""); //UpdateStr System.Text.StringBuilder sblistUpdateStr = new StringBuilder(); for (int i = 0; i < listUpdate.Count; i++) { if(listUpdate[i].IndexOf("CreateUser")>-1||listUpdate[i].IndexOf("CreateTime")>-1) continue; sblistUpdateStr.Append(listUpdate[i] + ""); if ((i + 1) % 5 == 0 && (i + 1)!=listUpdate.Count) sblistUpdateStr.Append(" "); } string listUpdateStr = System.Text.RegularExpressions.Regex.Replace(sblistUpdateStr.ToString(), @"^+s*"",", "+ ""); #> using System; using System.Collections.Generic; using System.Data; using ZS.Mix.Common; using <#= className #>.Entity; namespace <#= nameSpace #> { internal class <#= className+"DAL" #> : BaseDAL { /// <summary> /// 分页获取所有记录 /// </summary> public List<<#= className #>Entity> GetAll(<#= TypeUtil.DbType2TypeString(listPK[0].DataType) #> <#= listPK[0].Name #>, int pageindex, int pagesize) { string sql = string.Format(@"select <#=colAllStr #> from <#= table.Database.Name + "." + table.SchemaName + "." + table.Name #> t where t.<#= listPK[0].Name #> like '%' + '{0}' + '%' order by t.<#= listPK[0].Name #> offset ( {1} - 1 ) * {2} rows fetch next {2} rows only;", <#= listPK[0].Name #>, pageindex, pagesize); return Service.SqlTable(sql).ToList<<#= className+"Entity" #>>(); } /// <summary> /// 获取记录总数 /// </summary> public int GetCount(<#= TypeUtil.DbType2TypeString(listPK[0].DataType) #> <#= listPK[0].Name #>) { string sql = string.Format(@"select count(1) from <#= table.Database.Name + "." + table.SchemaName + "." + table.Name #> t where <#= listPK[0].Name #> like '%' + '{0}' + '%';", <#= listPK[0].Name #>); return Convert.ToInt32(Service.SqlValueList(sql)); } /// <summary> /// 获取简要记录 /// </summary> public List<<#= className #>Entity> Get<#= className #>List (<#= TypeUtil.DbType2TypeString(listPK[0].DataType) #> <#= listPK[0].Name #> = "%") { string sql = string.Format(@"select <#=colAllStr #> from <#= table.Database.Name + "." + table.SchemaName + "." + table.Name #> t where t.<#= listPK[0].Name #> like '%' + '{0}' + '%' order by t.<#= listPK[0].Name #>", <#= listPK[0].Name #>); return Service.SqlTable(sql).ToList<<#= className+"Entity" #>>(); } /// <summary> /// 获取单个实体 /// </summary> public <#= className #>Entity Get<#= className #> (<#= TypeUtil.DbType2TypeString(listPK[0].DataType) #> <#= listPK[0].Name #>) { string sql = string.Format(@" select <#=colAllStr #> from <#= table.Database.Name + "." + table.SchemaName + "." + table.Name #> t where t.<#= listPK[0].Name #> like '%' + '{0}' + '%';", <#= listPK[0].Name #>); return Service.SqlTable(sql).ToList<<#= className+"Entity" #>>()[0]; } /// <summary> /// 是否存在该记录 /// </summary> public bool Exists(<#= TypeUtil.DbType2TypeString(listPK[0].DataType) #> <#= listPK[0].Name #>) { string sql = string.Format(@"declare @TempID int;SELECT @TempID = count(1) from <#= table.Database.Name + "." + table.SchemaName + "." + table.Name #> t where <#= listPK[0].Name #> = '{0}'; if @TempID = 0 select 0; else select 1;", <#= listPK[0].Name #>); int result = Convert.ToInt32(Service.SqlValueList(sql)); return result == 1 ? true : false; } /// <summary> /// 增加一条数据 /// </summary> public bool Add(<#= className #>Entity entity) { string sql = @"INSERT INTO <#= table.Database.Name + "." + table.SchemaName + "." + table.Name #>( <#= colOtherStr #> )VALUES(" <#= listAddStr #> + " ) ;"; int rowsAffected = Service.SqlExecute(sql); return rowsAffected == 1 ? true : false; } /// <summary> /// 更新一条数据 /// </summary> public bool Update(<#= className #>Entity entity) { string sql = @"UPDATE <#= table.Database.Name + "." + table.SchemaName + "." + table.Name #> SET " <#= listUpdateStr #> + " WHERE <#=listPK[0].Name #>= '" + <#= "entity."+listPK[0].Name #> +"';"; int rowsAffected = Service.SqlExecute(sql); return rowsAffected == 1 ? true : false; } /// <summary> /// 删除一条数据 /// </summary> public bool Delete(<#= TypeUtil.DbType2TypeString(listPK[0].DataType) #> <#= listPK[0].Name #>) { string sql = string.Format(@"DELETE <#= table.Database.Name + "." + table.SchemaName + "." + table.Name #> WHERE <#= listPK[0].Name #>='{0}';", <#= listPK[0].Name #>); int rowsAffected = Service.SqlExecute(sql); return rowsAffected == 1 ? true : false; } }
3、注意这个处理null值的函数:
static public object SqlNull(dynamic obj) { if (obj == null) { return "null"; } string typename = obj.GetType().Name.Equals("Nullable`1") ? obj.GetType().GetGenericArguments()[0].ToString() : obj.GetType().ToString(); if (typename.Equals("System.String") || typename.Equals("System.Boolean")) { return "'" + obj.ToString() + "'"; } else if (typename.Equals("System.DateTime")) { return "convert(datetime,'" + obj.ToString() + "') "; } else { return obj.ToString(); } }