参考博文
http://www.cnblogs.com/heyuquan/archive/2012/07/26/2610959.html
图片释义
1、简单示例,对基本的模块标记
2、根据上图生成的类
网上大佬写的JuCheap项目中的T4模板
https://gitee.com/jucheap/projects
T4代码
<#@ template language="C#" debug="true" hostspecific="True"#> <#@ include file="$(SolutionDir)JuCheap.ServiceMultipleOutputHelper.ttinclude" #> <#@ import namespace="System"#> <# string solutionsPath = Host.ResolveAssemblyReference("$(SolutionDir)"); var files = System.IO.Directory.GetFiles(solutionsPath + @"JuCheap.Entity", "*.cs"); var manager = Manager.Create(Host, GenerationEnvironment); //1.开始输出接口契约文件 foreach (var filePath in files) { var file = new FileInfo(filePath); var name = file.Name.Replace("Entity.cs",string.Empty); var lowerName =name.ToLower(); //定义输出文件 manager.StartNewFile("I"+name+"Service.Partial.cs", string.Empty); #> /******************************************************************************* * Copyright (C) JuCheap.Com * * Author: dj.wong * Create Date: <#=DateTime.Now#> * Description: Automated building by service@JuCheap.com * * Revision History: * Date Author Description * 2017-08-02 dj.wong optimization *********************************************************************************/ using System; using System.Collections.Generic; using System.Linq.Expressions; using JuCheap.Service.Dto; namespace JuCheap.Service.Abstracts { /// <summary> /// <#=name#>业务契约 /// </summary> public partial interface I<#=name#>Service { /// <summary> /// 添加<#=lowerName#> /// </summary> /// <param name="<#=lowerName#>"><#=lowerName#>实体</param> /// <returns></returns> bool Add(<#=name#>Dto <#=lowerName#>); /// <summary> /// 批量添加<#=lowerName#> /// </summary> /// <param name="models"><#=lowerName#>集合</param> /// <returns></returns> bool Add(List<<#=name#>Dto> models); /// <summary> /// 编辑<#=lowerName#> /// </summary> /// <param name="<#=lowerName#>">实体</param> /// <returns></returns> bool Update(<#=name#>Dto <#=lowerName#>); /// <summary> /// 批量更新<#=lowerName#> /// </summary> /// <param name="<#=lowerName#>s"><#=lowerName#>实体集合</param> /// <returns></returns> bool Update(IEnumerable<<#=name#>Dto> <#=lowerName#>s); /// <summary> /// 删除<#=lowerName#> /// </summary> /// <param name="id">Id</param> /// <returns></returns> bool Delete(int id); /// <summary> /// 批量删除<#=lowerName#> /// </summary> /// <param name="exp">条件表达式</param> /// <returns></returns> bool Delete(Expression<Func<<#=name#>Dto, bool>> exp); /// <summary> /// 获取单条符合条件的 <#=lowerName#> 数据 /// </summary> /// <param name="exp">条件表达式</param> /// <returns></returns> <#=name#>Dto GetOne(Expression<Func<<#=name#>Dto, bool>> exp); /// <summary> /// 查询符合调价的 <#=lowerName#> /// </summary> /// <param name="exp">过滤条件</param> /// <param name="orderExp">排序条件</param> /// <param name="isDesc">是否是降序排列</param> /// <returns></returns> List<<#=name#>Dto> Query<OrderKeyType>(Expression<Func<<#=name#>Dto, bool>> exp, Expression<Func<<#=name#>Dto, OrderKeyType>> orderExp, bool isDesc = true); /// <summary> /// 分页获取<#=lowerName#> /// </summary> /// <param name="queryBase">QueryBase</param> /// <param name="exp">过滤条件</param> /// <param name="orderExp">排序条件</param> /// <param name="isDesc">是否是降序排列</param> /// <returns></returns> ResultDto<<#=name#>Dto> GetWithPages<OrderKeyType>(QueryBase queryBase, Expression<Func<<#=name#>Dto, bool>> exp, Expression<Func<<#=name#>Dto, OrderKeyType>> orderExp, bool isDesc = true); /// <summary> /// 分页获取<#=lowerName#> /// </summary> /// <param name="queryBase">QueryBase</param> /// <param name="exp">过滤条件</param> /// <param name="orderBy">排序条件</param> /// <param name="orderDir">是否是降序排列</param> /// <returns></returns> ResultDto<<#=name#>Dto> GetWithPages(QueryBase queryBase, Expression<Func<<#=name#>Dto, bool>> exp, string orderBy, string orderDir = "desc"); } } <# // 结束输出文件 manager.EndBlock(); } //2.开始生成契约实现文件 foreach (var filePath in files) { var file = new FileInfo(filePath); var name = file.Name.Replace("Entity.cs",string.Empty); var lowerName = name.ToLower(); //定义输出文件 manager.StartNewFile(name+"Service.Partial.cs", string.Empty); #> /******************************************************************************* * Copyright (C) JuCheap.Com * * Author: dj.wong * Create Date: <#=DateTime.Now#> * Description: Automated building by service@JuCheap.com * * Revision History: * Date Author Description * *********************************************************************************/ using System; using System.Collections.Generic; using System.Data.Entity.Migrations; using System.Linq; using System.Linq.Expressions; using EntityFramework.Extensions; using AutoMapper; using JuCheap.Core; using JuCheap.Core.Extentions; using JuCheap.Entity; using JuCheap.Service.Dto; using Mehdime.Entity; namespace JuCheap.Service.Abstracts { /// <summary> /// <#=name#>业务契约 /// </summary> public partial class <#=name#>Service : ServiceBase<<#=name#>Entity>, IDependency, I<#=name#>Service { #region 构造函数注册上下文 public IDbContextScopeFactory _dbScopeFactory {get;set;} //private readonly IDbContextScopeFactory _dbScopeFactory; //public <#=name#>Service(IDbContextScopeFactory dbScopeFactory) //{ // _dbScopeFactory = dbScopeFactory; //} #endregion #region I<#=name#>Service 接口实现 /// <summary> /// 添加<#=lowerName#> /// </summary> /// <param name="dto"><#=lowerName#>实体</param> /// <returns></returns> public bool Add(<#=name#>Dto dto) { using (var scope = _dbScopeFactory.Create()) { var db = GetDb(scope); var dbSet = GetDbSet(db); var entity = Mapper.Map<<#=name#>Dto, <#=name#>Entity>(dto); dbSet.Add(entity); var count = db.SaveChanges(); return count > 0; } } /// <summary> /// 批量添加<#=lowerName#> /// </summary> /// <param name="dtos"><#=lowerName#>集合</param> /// <returns></returns> public bool Add(List<<#=name#>Dto> dtos) { using (var scope = _dbScopeFactory.Create()) { var db = GetDb(scope); var dbSet = GetDbSet(db); var entities = Mapper.Map<List<<#=name#>Dto>, List<<#=name#>Entity>>(dtos); dbSet.AddRange(entities); return db.SaveChanges() > 0; } } /// <summary> /// 编辑<#=lowerName#> /// </summary> /// <param name="dto">实体</param> /// <returns></returns> public bool Update(<#=name#>Dto dto) { using (var scope = _dbScopeFactory.Create()) { var db = GetDb(scope); var dbSet = GetDbSet(db); var entity = Mapper.Map<<#=name#>Dto, <#=name#>Entity>(dto); dbSet.AddOrUpdate(entity); return db.SaveChanges() > 0; } } /// <summary> /// 批量更新<#=lowerName#> /// </summary> /// <param name="dtos"><#=lowerName#>实体集合</param> /// <returns></returns> public bool Update(IEnumerable<<#=name#>Dto> dtos) { using (var scope = _dbScopeFactory.Create()) { var db = GetDb(scope); var dbSet = GetDbSet(db); var entities = Mapper.Map<IEnumerable<<#=name#>Dto>, IEnumerable<<#=name#>Entity>>(dtos); dbSet.AddOrUpdate(entities.ToArray()); return db.SaveChanges() > 0; } } /// <summary> /// 删除<#=lowerName#> /// </summary> /// <param name="id">Id</param> /// <returns></returns> public bool Delete(int id) { using (var scope = _dbScopeFactory.Create()) { var db = GetDb(scope); var dbSet = GetDbSet(db); var model = dbSet.FirstOrDefault(item => item.Id == id); dbSet.Remove(model); return db.SaveChanges() > 0; } } /// <summary> /// 批量删除<#=lowerName#> /// </summary> /// <param name="exp">条件表达式</param> /// <returns></returns> public bool Delete(Expression<Func<<#=name#>Dto, bool>> exp) { using (var scope = _dbScopeFactory.Create()) { var db = GetDb(scope); var dbSet = GetDbSet(db); var where = exp.Cast<<#=name#>Dto, <#=name#>Entity, bool>(); var models = dbSet.Where(where); dbSet.RemoveRange(models); return db.SaveChanges() > 0; } } /// <summary> /// 获取单条符合条件的 <#=lowerName#> 数据 /// </summary> /// <param name="exp">条件表达式</param> /// <returns></returns> public <#=name#>Dto GetOne(Expression<Func<<#=name#>Dto, bool>> exp) { using (var scope = _dbScopeFactory.CreateReadOnly()) { var db = GetDb(scope); var dbSet = GetDbSet(db); var where = exp.Cast<<#=name#>Dto, <#=name#>Entity, bool>(); var entity = dbSet.AsNoTracking().FirstOrDefault(where); return Mapper.Map<<#=name#>Entity, <#=name#>Dto>(entity); } } /// <summary> /// 查询符合调价的 <#=lowerName#> /// </summary> /// <param name="exp">过滤条件</param> /// <param name="orderExp">排序条件</param> /// <param name="isDesc">是否是降序排列</param> /// <returns></returns> public List<<#=name#>Dto> Query<OrderKeyType>(Expression<Func<<#=name#>Dto, bool>> exp, Expression<Func<<#=name#>Dto, OrderKeyType>> orderExp, bool isDesc = true) { using (var scope = _dbScopeFactory.CreateReadOnly()) { var db = GetDb(scope); var dbSet = GetDbSet(db); var where = exp.Cast<<#=name#>Dto, <#=name#>Entity, bool>(); var order = orderExp.Cast<<#=name#>Dto, <#=name#>Entity, OrderKeyType>(); var query = GetQuery(dbSet, where, order, isDesc); var list = query.ToList(); return Mapper.Map<List<<#=name#>Entity>, List<<#=name#>Dto>>(list); } } /// <summary> /// 分页获取<#=name#> /// </summary> /// <param name="queryBase">QueryBase</param> /// <param name="exp">过滤条件</param> /// <param name="orderExp">排序条件</param> /// <param name="isDesc">是否是降序排列</param> /// <returns></returns> public ResultDto<<#=name#>Dto> GetWithPages<OrderKeyType>(QueryBase queryBase, Expression<Func<<#=name#>Dto, bool>> exp, Expression<Func<<#=name#>Dto, OrderKeyType>> orderExp, bool isDesc = true) { using (var scope = _dbScopeFactory.CreateReadOnly()) { var db = GetDb(scope); var dbSet = GetDbSet(db); var where = exp.Cast<<#=name#>Dto, <#=name#>Entity, bool>(); var order = orderExp.Cast<<#=name#>Dto, <#=name#>Entity, OrderKeyType>(); var query = GetQuery(dbSet, where, order, isDesc); var query_count = query.FutureCount(); var query_list = query.Skip(queryBase.Start).Take(queryBase.Length).Future(); var list = query_list.ToList(); var dto = new ResultDto<<#=name#>Dto> { recordsTotal = query_count.Value, data = Mapper.Map<List<<#=name#>Entity>, List<<#=name#>Dto>>(list) }; return dto; } } /// <summary> /// 分页获取<#=name#> /// </summary> /// <param name="queryBase">QueryBase</param> /// <param name="exp">过滤条件</param> /// <param name="orderBy">排序条件</param> /// <param name="orderDir">排序类型:desc(默认)/asc</param> /// <returns></returns> public ResultDto<<#=name#>Dto> GetWithPages(QueryBase queryBase, Expression<Func<<#=name#>Dto, bool>> exp, string orderBy, string orderDir = "desc") { using (var scope = _dbScopeFactory.CreateReadOnly()) { var db = GetDb(scope); var dbSet = GetDbSet(db); var where = exp.Cast<<#=name#>Dto, <#=name#>Entity, bool>(); //var order = orderExp.Cast<<#=name#>Dto, <#=name#>Entity, OrderKeyType>(); var query = GetQuery(dbSet, where, orderBy, orderDir); var query_count = query.FutureCount(); var query_list = query.Skip(queryBase.Start).Take(queryBase.Length).Future(); var list = query_list.ToList(); var dto = new ResultDto<<#=name#>Dto> { recordsTotal = query_count.Value, data = Mapper.Map<List<<#=name#>Entity>, List<<#=name#>Dto>>(list) }; return dto; } } #endregion } } <# // 结束输出文件 manager.EndBlock(); } #> <# manager.StartNewFile("AutoMapperConfiguration.Partial.cs", string.Empty); #> /******************************************************************************* * Copyright (C) JuCheap.Com * * Author: dj.wong * Create Date: 2015/8/7 11:12:12 * Description: Automated building by service@JuCheap.com * * Revision History: * Date Author Description * *********************************************************************************/ using AutoMapper; using JuCheap.Entity; using JuCheap.Service.Dto; namespace JuCheap.Service { /// <summary> /// AutoMapper 配置 /// </summary> public partial class AutoMapperConfiguration { /// <summary> /// 配置AutoMapper /// </summary> public static void Config() { <# //1.开始输出接口契约文件 foreach (var filePath in files) { var file = new FileInfo(filePath); var name = file.Name.Replace("Entity.cs",string.Empty); var lowerName =name.ToLower(); //定义输出文件 #> Mapper.CreateMap<<#=name#>Entity, <#=name#>Dto>(); Mapper.CreateMap<<#=name#>Dto, <#=name#>Entity>(); <# } #> } } } <# // 结束输出文件 manager.EndBlock(); // 执行编译 manager.Process(true); #>
引用的ttInclude代码
<#@ assembly name="System.Core" #><#@ assembly name="System.Data.Linq" #><#@ assembly name="EnvDTE" #><#@ assembly name="System.Xml" #><#@ assembly name="System.Xml.Linq" #><#@ import namespace="System.Collections.Generic" #><#@ import namespace="System.IO" #><#@ import namespace="System.Text" #><#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#+ // https://raw.github.com/damieng/DamienGKit // http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited // Manager class records the various blocks so it can split them up class Manager { private class Block { public String Name; public string FilePath; public int Start, Length; public bool IncludeInDefault; } private Block currentBlock; private readonly List<Block> files = new List<Block>(); private readonly Block footer = new Block(); private readonly Block header = new Block(); private readonly ITextTemplatingEngineHost host; private readonly StringBuilder template; protected readonly List<String> generatedFileNames = new List<String>(); public static Manager Create(ITextTemplatingEngineHost host, StringBuilder template) { return (host is IServiceProvider) ? new VSManager(host, template) : new Manager(host, template); } public void StartNewFile(String name, string filePath) { if (name == null) throw new ArgumentNullException("name"); CurrentBlock = new Block { Name = name, FilePath = filePath }; } public void StartFooter(bool includeInDefault = true) { CurrentBlock = footer; footer.IncludeInDefault = includeInDefault; } public void StartHeader(bool includeInDefault = true) { CurrentBlock = header; header.IncludeInDefault = includeInDefault; } public void EndBlock() { if (CurrentBlock == null) return; CurrentBlock.Length = template.Length - CurrentBlock.Start; if (CurrentBlock != header && CurrentBlock != footer) files.Add(CurrentBlock); currentBlock = null; } public virtual void Process(bool split, bool sync = true) { if (split) { EndBlock(); String headerText = template.ToString(header.Start, header.Length); String footerText = template.ToString(footer.Start, footer.Length); String outputPath = Path.GetDirectoryName(host.TemplateFile); files.Reverse(); if (!footer.IncludeInDefault) template.Remove(footer.Start, footer.Length); foreach(Block block in files) { String myPath = block.FilePath; if(!string.IsNullOrWhiteSpace(myPath)) outputPath = myPath; String fileName = Path.Combine(outputPath, block.Name); String content = headerText + template.ToString(block.Start, block.Length) + footerText; generatedFileNames.Add(fileName); CreateFile(fileName, content); template.Remove(block.Start, block.Length); } if (!header.IncludeInDefault) template.Remove(header.Start, header.Length); } } protected virtual void CreateFile(String fileName, String content) { if (IsFileContentDifferent(fileName, content)) File.WriteAllText(fileName, content); } public virtual String GetCustomToolNamespace(String fileName) { return null; } public virtual String DefaultProjectNamespace { get { return null; } } protected bool IsFileContentDifferent(String fileName, String newContent) { return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent); } private Manager(ITextTemplatingEngineHost host, StringBuilder template) { this.host = host; this.template = template; } private Block CurrentBlock { get { return currentBlock; } set { if (CurrentBlock != null) EndBlock(); if (value != null) value.Start = template.Length; currentBlock = value; } } private class VSManager: Manager { private readonly EnvDTE.ProjectItem templateProjectItem; private readonly EnvDTE.DTE dte; private readonly Action<String> checkOutAction; private readonly Action<List<String>> projectSyncAction; public override String DefaultProjectNamespace { get { return templateProjectItem.ContainingProject.Properties.Item("DefaultNamespace").Value.ToString(); } } public override String GetCustomToolNamespace(string fileName) { return dte.Solution.FindProjectItem(fileName).Properties.Item("CustomToolNamespace").Value.ToString(); } public override void Process(bool split, bool sync) { if (templateProjectItem.ProjectItems == null) return; base.Process(split, sync); if (sync) projectSyncAction.EndInvoke(projectSyncAction.BeginInvoke(generatedFileNames, null, null)); } protected override void CreateFile(String fileName, String content) { if (IsFileContentDifferent(fileName, content)) { CheckoutFileIfRequired(fileName); File.WriteAllText(fileName, content); } } internal VSManager(ITextTemplatingEngineHost host, StringBuilder template) : base(host, template) { var hostServiceProvider = (IServiceProvider)host; if (hostServiceProvider == null) throw new ArgumentNullException("Could not obtain IServiceProvider"); 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); checkOutAction = fileName => dte.SourceControl.CheckOutItem(fileName); projectSyncAction = keepFileNames => ProjectSync(templateProjectItem, keepFileNames); } private static void ProjectSync(EnvDTE.ProjectItem templateProjectItem, List<String> keepFileNames) { var keepFileNameSet = new HashSet<String>(keepFileNames); var projectFiles = new Dictionary<String, EnvDTE.ProjectItem>(); var originalFilePrefix = Path.GetFileNameWithoutExtension(templateProjectItem.FileNames[0]) + "."; foreach (EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems) projectFiles.Add(projectItem.FileNames[0], projectItem); // Remove unused items from the project foreach (var pair in projectFiles) if (!keepFileNames.Contains(pair.Key) && !(Path.GetFileNameWithoutExtension(pair.Key) + ".").StartsWith(originalFilePrefix)) pair.Value.Delete(); // Add missing files to the project foreach(String fileName in keepFileNameSet) if (!projectFiles.ContainsKey(fileName)) templateProjectItem.ProjectItems.AddFromFile(fileName); } private void CheckoutFileIfRequired(String fileName) { var sc = dte.SourceControl; if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName)) checkOutAction.EndInvoke(checkOutAction.BeginInvoke(fileName, null, null)); } } } #>
用到的语法:
1、string solutionsPath = Host.ResolveAssemblyReference("$(SolutionDir)");
T4模板中获取当前解决方案的文件路径。
2、var files = System.IO.Directory.GetFiles(solutionsPath + @"JuCheap.Entity", "*.cs");
获取JuCheap.Entity项目中所有cs扩展名的文件信息。
3、var manager = Manager.Create(Host, GenerationEnvironment);
在引用的ttInclude中,创建一个模板管理者的对象,用于操作和生成数据。
4、 public partial interface I<#=name#>Service
在<# string name="ClassName" #> 中声明的变量,
可以在文本块中以<#=name#>的形式调用。(name为声明的变量名称)
5、 manager.EndBlock();
在引用的ttInclude中,结束对新服务类的内容赋值,
将文件对象添加到文件集合中。
6、manager.Process(true);
在引用的ttInclude中,结束数据生成,编译运行,生成相应的文件。
总结
因为有CodeSmith的使用经验,所以在看到T4模板使用方法时,
没有想象的那么困难。
纸上得来终觉浅,绝知此事要躬行。
目前只是看网上大佬的教程,还没有在项目中实际使用过,
还有许多未知的问题和操作。