T4文本模板生成EasyModular各层代码
先不说T4如何生成代码,有人看到这到这标题肯定会问T4模板我知道啊,但这EasyModular又是啥东东呢。关于什么是EasyModular,点一下这个链接:EasyModular,里面藏着一些小确幸。
熟悉T4文本模板
T4是什么
T4模板全称是Text Template Transformation Toolkit,在 Visual Studio 中,T4 文本模板是文本块与可生成文本文件的控制逻辑的混合体,所生成的文件可以是任何类型的文本,例如网页、资源文件或任何语言的程序源代码。简单来说T4文本模板可以生成任何你想要的模板文件。
T4语法
T4语法和C#语法有点相似,主要分为三类
- 指令:控制如何处理模板的元素
- 文本块:直接复制到输出的内容
- 控制块:将变量值插入的文本中并控制文本的有条件或重复部分的程序代码
指令
- 模板指令
<#@ template debug="false" hostspecific="false" language="C#" #> - debug:是否启用调试,有效值true、false,默认为false。
- hostspecific:有效值true、false,默认为false。如果将此特性的值设置为 true,则会将名为 Host 的属性添加到由文本模板生成的类中。 该属性是对转换引擎的宿主的引用,并声明为Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost。
- language:输出语言,有效值C#、VB,默认为C#
还要很多其他的指令,这里就不详细铺开了,有兴趣的可以到官网查阅。
控制块
<# 标准控制块 #> 可以包含语句。
<#= 表达式控制块 #> 可以包含表达式。
<#+ 类特征控制块 #> 可以包含方法、字段和属性,就像一个类的内部 。
生成EasyModular各层代码
上面我们简单熟悉了一下T4文本模板,接下来我们看一下如何用T4文本模板生成生成EasyModular各层代码。大概的思路如下:
- 先熟悉EasyModular各层代码的结构,确定哪些需要动态生成代码块。
- 创建一个访问数据库的辅助模板类,因为我们需要访问数据库的表和字段的信息
- 创建一个文件输出的辅助模板类,因为我们需要输出多个文件
- 创建EasyModular每一层的T4文本模板
整个T4模板的代码架构如下:

数据库的辅助模板类
<#@ include file="$(ProjectDir)/Model/TableModel.ttinclude"#>
<#@ include file="$(ProjectDir)/Model/ColumnModel.ttinclude"#>
<#+
/// <summary>
/// MySql数据库访问辅助类
/// </summary>
public class MySqlDbHelper
{
private string _dbName = "";
public string _ConnStr = "";
public MySqlDbHelper(string projectDir)
{
var url = Path.Combine(projectDir, "config.json");
using var streamReader = new StreamReader(url);
var config = JsonConvert.DeserializeObject<ConfigModel>(streamReader.ReadToEnd());
_ConnStr= config.ConnectionString;
_dbName = config.DbName;
}
/// <summary>
/// 获取表信息
/// </summary>
/// <param name="prefix">前缀</param>
/// <param name="tbName">表名</param>
/// <returns></returns>
public List<TableModel> GetDbTables(string prefix = "", string tbName = "")
{
var strSql = $"SELECT table_name,table_comment FROM information_schema.TABLES WHERE table_schema = '{_dbName}'";
if (!string.IsNullOrEmpty(prefix))
strSql += $" and table_name like '{prefix}%'";
if (!string.IsNullOrEmpty(tbName))
strSql += $" and table_name = '{tbName}'";
return SqlQuery<TableModel>(strSql);
}
/// <summary>
/// 获取列信息
/// </summary>
/// <param name="tbName">表名</param>
/// <returns></returns>
public List<ColumnModel> GetDbColumns(string tbName)
{
var strSql = $"SELECT column_name, column_type, column_comment, data_type,is_nullable FROM information_schema.COLUMNS WHERE table_name = '{tbName}' AND table_schema = '{_dbName}' ORDER BY ordinal_position ASC";
return SqlQuery<ColumnModel>(strSql);
}
/// <summary>
/// 数据库类型转换为C#类型
/// </summary>
/// <param name="dbtype"></param>
/// <param name="is_nullable"> 是否可空(YES或NO)</param>
/// <returns></returns>
public string ConvertToCsharpType(string dbtype, string is_nullable)
{
if (string.IsNullOrEmpty(dbtype))
return dbtype;
dbtype = dbtype.ToLower();
var csharpType = "object";
switch (dbtype)
{
case "bigint": csharpType = "long"; break;
case "binary": csharpType = "byte[]"; break;
case "bit": csharpType = "bool"; break;
case "char": csharpType = "string"; break;
case "date": csharpType = "DateTime"; break;
case "datetime": csharpType = "DateTime"; break;
case "datetime2": csharpType = "DateTime"; break;
case "datetimeoffset": csharpType = "DateTimeOffset"; break;
case "decimal": csharpType = "decimal"; break;
case "float": csharpType = "double"; break;
case "image": csharpType = "byte[]"; break;
case "int": csharpType = "int"; break;
case "money": csharpType = "decimal"; break;
case "nchar": csharpType = "string"; break;
case "ntext": csharpType = "string"; break;
case "numeric": csharpType = "decimal"; break;
case "nvarchar": csharpType = "string"; break;
case "real": csharpType = "Single"; break;
case "smalldatetime": csharpType = "DateTime"; break;
case "smallint": csharpType = "short"; break;
case "smallmoney": csharpType = "decimal"; break;
case "sql_variant": csharpType = "object"; break;
case "sysname": csharpType = "object"; break;
case "text": csharpType = "string"; break;
case "time": csharpType = "TimeSpan"; break;
case "timestamp": csharpType = "byte[]"; break;
case "tinyint": csharpType = "byte"; break;
case "uniqueidentifier": csharpType = "Guid"; break;
case "varbinary": csharpType = "byte[]"; break;
case "varchar": csharpType = "string"; break;
case "xml": csharpType = "string"; break;
default: csharpType = "object"; break;
}
if(is_nullable=="YES"&&csharpType!="string"&&csharpType!="object")
{
csharpType=csharpType+"?";
}
return csharpType;
}
/// <summary>
/// 查询
/// </summary>
/// <param name="sql"></param>
/// <returns></returns>
public List<T> SqlQuery<T>(string sql, params MySqlParameter[] paras) where T : new()
{
using (MySqlConnection con = new MySqlConnection(_ConnStr))
{
MySqlDataAdapter sqlda = new MySqlDataAdapter(sql, con);
sqlda.SelectCommand.Parameters.AddRange(paras);
DataTable dt = new DataTable();
sqlda.Fill(dt);
return DataTableToList<T>(dt);
}
}
/// <summary>
/// 利用反射将Datatable转换为List<T>对象
/// </summary>
/// <typeparam name="T">集合</typeparam>
/// <param name="dt"> datatable对象</param>
/// <returns></returns>
public List<T> DataTableToList<T>(DataTable dt) where T : new()
{
//定义集合
List<T> ts = new List<T>();
//遍历dataTable中的数据行
foreach (DataRow dr in dt.Rows)
{
T t = new T();
//获得此模型的公共属性
PropertyInfo[] propertys = t.GetType().GetProperties();
//遍历该对象的所有属性
foreach (PropertyInfo pi in propertys)
{
string tempName = pi.Name;
if (!dt.Columns.Contains(tempName)) continue; //检查datatable是否包含此列(列名==对象的属性名)
object value = dr[tempName]; //取值
if (value == DBNull.Value) continue; //如果非空,则赋给对象的属性
pi.SetValue(t, value, null);
}
//对象添加到泛型集合中
ts.Add(t);
}
return ts;
}
}
#>
文件输出的辅助模板类
<#@ 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"#>
<#+
class Manager
{
public struct Block {
public int Start, Length;
public String Name,OutputPath;
}
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 Manager(ITextTemplatingEngineHost host, StringBuilder template, bool commonHeader) {
this.host = host;
this.template = template;
strategy = ManagementStrategy.Create(host);
}
public void StartBlock(String name,String outputPath) {
currentBlock = new Block { Name = name, Start = template.Length ,OutputPath=outputPath};
}
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(block.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;
}
}
}
}#>
EasyModular.Domain模板类
<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ assembly name="System.Data"#>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="Newtonsoft.Json" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="MySql.Data.MySqlClient" #>
<#@ assembly name="$(ProjectDir)\_Library\MySql.Data.dll" #>
<#@ assembly name="$(ProjectDir)\_Library\Newtonsoft.Json.dll" #>
<#@ include file="$(ProjectDir)/Model/ConfigModel.ttinclude"#>
<#@ include file="$(ProjectDir)/Helper/OutputHelper.ttinclude"#>
<#@ include file="$(ProjectDir)/Helper/MySqlDbHelper.ttinclude"#>
<# var manager = new Manager(Host, GenerationEnvironment, true); #>
<#
var solutionDir = Host.ResolveAssemblyReference("$(SolutionDir)"); //当解决方案目录
var projectDir = Host.ResolveAssemblyReference("$(ProjectDir)"); //当前项目目录
var configUrl = Path.Combine(projectDir, "config.json");
using var streamReader = new StreamReader(configUrl);
var config = JsonConvert.DeserializeObject<ConfigModel>(streamReader.ReadToEnd());
var projectName=$"{config.Module}.Domain";
var projectPath=Path.Combine(solutionDir,projectName);
if (!Directory.Exists(projectPath))
{
Directory.CreateDirectory(projectPath);
}
var _db =new MySqlDbHelper(projectDir);
var tables=_db.GetDbTables(config.Prefix,config.TableName);
foreach(var table in tables)
{
var tableName = table.table_name.Substring(config.Prefix.Length - 1).Replace("_", "");;
var domainDir=Path.Combine(projectPath,tableName);
if(!Directory.Exists(domainDir))
{
Directory.CreateDirectory(domainDir);
}
//===========================1.生成实体===========================================
manager.StartBlock(tableName+"Entity.cs",domainDir);
#>
using EasyModular.SqlSugar;
using SqlSugar;
using System;
using System.Collections.Generic;
using System.Text;
namespace <#=projectName#>
{
/// <summary>
/// <#=table.table_comment.Replace("表","")#>
/// </summary>
[SugarTable("<#=table.table_name#>")]
public partial class <#=tableName#>Entity : EntityBaseForEnterprise
{
<#
var columns=_db.GetDbColumns(table.table_name);
foreach(var col in columns)
{
if (config.ExcludeCols.Contains(col.column_name))
continue;
var colType=_db.ConvertToCsharpType(col.data_type,col.is_nullable);
#>
/// <summary>
/// <#=col.column_comment#>
/// </summary>
public <#=colType#> <#=col.column_name#> { get; set; }
<#
}
#>
}
}
<#
manager.EndBlock();
//===========================2.生成模型===========================================
var modelDir=Path.Combine(domainDir,"Models");
if(!Directory.Exists(modelDir))
{
Directory.CreateDirectory(modelDir);
}
manager.StartBlock(tableName+"QueryModel.cs",modelDir);
#>
using EasyModular.Utils;
using System;
using System.Collections.Generic;
using System.Text;
namespace <#=projectName#>
{
/// <summary>
/// <#=table.table_comment#>查询模型
/// </summary>
public class <#=tableName#>QueryModel : QueryPagingModel
{
<#
foreach(var col in columns)
{
if (config.ExcludeCols.Contains(col.column_name))
continue;
var colType=_db.ConvertToCsharpType(col.data_type,col.is_nullable);
#>
/// <summary>
/// <#=col.column_comment#>
/// </summary>
public <#=colType#> <#=col.column_name#> { get; set; }
<#}
#>
}
}
<#
manager.EndBlock();
//===========================3.生成仓储接口===========================================
manager.StartBlock("I"+tableName+"Repository.cs",domainDir);
#>
using EasyModular.SqlSugar;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace <#=projectName#>
{
public interface I<#=tableName#>Repository : IRepository<<#=tableName#>Entity>
{
Task<IList<<#=tableName#>Entity>> Query(<#=tableName#>QueryModel model, Guid enterpriseId);
}
}
<#
manager.EndBlock();
}
manager.Process(true);
#>
配置文件
{
"Module": "EDCP.Admin", //模块
"DbName": "EDCP", //数据库名称
"Prefix": "Sys_", //表前缀
"TableName": "", //表名
"ConnectionString": "server=XX;user id=XX; password=XXX; database=XXX;", //数据库连接串
"ExcludeCols": "EnterpriseId,Creater,CreaterName,CreatedTime,Modifier,ModifierName,ModifiedTime,IsDel,Deleter,DeleterName,DeletedTime" //排除的字段
}
后续扩展
- 目前仅支持MySql的模板生成,后面需要兼容其他主流的数据库。
- 支持可视化的代码生成。
C#

浙公网安备 33010602011771号