如何通过代码生成类似Visual Studio的CRUD样板代码及完整应用包?
嘿,我完全懂你想要的那种开箱即用的CRUD生成体验——就像VS自带的Account Controller样板那样,一键搞定从数据库实体到API端点的全套代码,连引用、配置都帮你弄好对吧?下面我一步步给你拆解怎么用T4模板实现这个目标:
T4本质是一种文本生成工具,它能通过C#代码读取数据库元数据(表结构、字段、主键这些),然后根据你定义的模板格式,批量生成对应的代码文件。核心思路就是:读取数据库结构 → 填充到预定义的代码模板 → 输出完整的应用代码包。
1. 第一步:读取数据库元数据(核心基础)
要生成对应表的代码,首先得让T4能连接你的数据库,获取表、列、主键等信息。下面是一段基础的T4代码示例,用来读取SQL Server的表结构:
<#@ assembly name="System.Data" #> <#@ import namespace="System.Data" #> <#@ import namespace="System.Data.SqlClient" #> <#@ output extension=".cs" #> <# // 替换成你的数据库连接字符串 string connectionString = @"Server=.;Database=YourTargetDB;Trusted_Connection=True;"; // 替换成你的应用名称 string appName = "YourAwesomeApp"; List<string> tableNames = new List<string>(); using (SqlConnection conn = new SqlConnection(connectionString)) { conn.Open(); // 只获取用户自定义的基础表,排除系统表 DataTable tables = conn.GetSchema("Tables", new string[] { null, null, null, "BASE TABLE" }); foreach (DataRow tableRow in tables.Rows) { string tableName = tableRow["TABLE_NAME"].ToString(); // 跳过系统表(比如sys开头的) if (tableName.StartsWith("sys")) continue; tableNames.Add(tableName); // 获取当前表的列信息 DataTable columns = conn.GetSchema("Columns", new string[] { null, null, tableName }); // 获取当前表的主键信息 DataTable primaryKeys = conn.GetSchema("PrimaryKeys", new string[] { null, null, tableName }); string pkColumnName = primaryKeys.Rows.Count > 0 ? primaryKeys.Rows[0]["COLUMN_NAME"].ToString() : "Id"; // 接下来就用这些元数据生成各类代码 GenerateModel(tableName, columns, appName); GenerateRepository(tableName, pkColumnName, appName); GenerateController(tableName, pkColumnName, appName); } // 生成DbContext GenerateDbContext(appName, tableNames); } // 辅助方法:把SQL数据类型转成C#类型 string GetCSharpType(string sqlType, object maxLength) { return sqlType switch { "int" => "int", "varchar" or "nvarchar" => "string", "datetime" or "datetime2" => "DateTime", "bit" => "bool", "uniqueidentifier" => "Guid", "bigint" => "long", // 可以根据你的数据库类型扩展更多映射 _ => "object" }; } // 生成实体Model的方法 void GenerateModel(string tableName, DataTable columns, string appName) { #> namespace <#= appName #>.Models { public class <#= tableName #> { <# foreach (DataRow col in columns.Rows) { string colName = col["COLUMN_NAME"].ToString(); string csharpType = GetCSharpType(col["DATA_TYPE"].ToString(), col["CHARACTER_MAXIMUM_LENGTH"]); #> public <#= csharpType #> <#= colName #> { get; set; } <# } #> } } <# } #>
这段代码的核心是通过SqlConnection.GetSchema获取数据库的元数据,然后调用不同的生成方法输出各类文件。
2. 生成Repository层(数据访问逻辑)
接着写GenerateRepository方法,生成包含CRUD异步方法的Repository,和VS样板的风格保持一致:
<# // 生成Repository的方法 void GenerateRepository(string tableName, string pkName, string appName) { string pkType = GetCSharpType(columns.Rows.Find(row => row["COLUMN_NAME"].ToString() == pkName)["DATA_TYPE"].ToString(), null); #> namespace <#= appName #>.Repositories { public class <#= tableName #>Repository { private readonly <#= appName #>.Data.AppDbContext _context; public <#= tableName #>Repository(<#= appName #>.Data.AppDbContext context) { _context = context; } public async Task<<#= tableName #>> GetByIdAsync(<#= pkType #> id) { return await _context.<#= tableName #>.FindAsync(id); } public async Task<IEnumerable<<#= tableName #>>> GetAllAsync() { return await _context.<#= tableName #>.ToListAsync(); } public async Task CreateAsync(<#= tableName #> entity) { _context.<#= tableName #>.Add(entity); await _context.SaveChangesAsync(); } public async Task UpdateAsync(<#= tableName #> entity) { _context.Entry(entity).State = EntityState.Modified; await _context.SaveChangesAsync(); } public async Task DeleteAsync(<#= pkType #> id) { var entity = await GetByIdAsync(id); if (entity != null) { _context.<#= tableName #>.Remove(entity); await _context.SaveChangesAsync(); } } } } <# } #>
这里默认用EF Core作为数据访问框架,你也可以改成ADO.NET或者其他ORM,只需要调整方法内的实现逻辑。
3. 生成API Controller(带完整端点)
然后是GenerateController方法,生成类似Account Controller的API控制器,包含标准的HTTP端点:
<# // 生成Controller的方法 void GenerateController(string tableName, string pkName, string appName) { string pkType = GetCSharpType(columns.Rows.Find(row => row["COLUMN_NAME"].ToString() == pkName)["DATA_TYPE"].ToString(), null); #> using Microsoft.AspNetCore.Mvc; using <#= appName #>.Models; using <#= appName #>.Repositories; using System.Collections.Generic; using System.Threading.Tasks; namespace <#= appName #>.Controllers { [Route("api/[controller]")] [ApiController] public class <#= tableName #>Controller : ControllerBase { private readonly <#= tableName #>Repository _repository; public <#= tableName #>Controller(<#= tableName #>Repository repository) { _repository = repository; } /// <summary>获取所有<#= tableName #>数据</summary> [HttpGet] public async Task<ActionResult<IEnumerable<<#= tableName #>>>> GetAll() { var items = await _repository.GetAllAsync(); return Ok(items); } /// <summary>根据ID获取单个<#= tableName #></summary> [HttpGet("{id}")] public async Task<ActionResult<<#= tableName #>>> GetById(<#= pkType #> id) { var item = await _repository.GetByIdAsync(id); if (item == null) { return NotFound(); } return item; } /// <summary>创建新的<#= tableName #></summary> [HttpPost] public async Task<IActionResult> Create(<#= tableName #> entity) { if (!ModelState.IsValid) { return BadRequest(ModelState); } await _repository.CreateAsync(entity); return CreatedAtAction(nameof(GetById), new { id = entity.<#= pkName #> }, entity); } /// <summary>更新<#= tableName #>数据</summary> [HttpPut("{id}")] public async Task<IActionResult> Update(<#= pkType #> id, <#= tableName #> entity) { if (!id.Equals(entity.<#= pkName #>)) { return BadRequest(); } await _repository.UpdateAsync(entity); return NoContent(); } /// <summary>删除<#= tableName #>数据</summary> [HttpDelete("{id}")] public async Task<IActionResult> Delete(<#= pkType #> id) { await _repository.DeleteAsync(id); return NoContent(); } } } <# } #>
这段代码生成的控制器包含了标准的RESTful端点,还加上了XML注释和基本的参数校验,和VS自带的样板风格一致。
4. 生成应用配置与引用
最后,你还可以在模板里生成一些基础配置文件的片段:
<# // 生成DbContext的方法 void GenerateDbContext(string appName, List<string> tableNames) { #> using Microsoft.EntityFrameworkCore; using <#= appName #>.Models; namespace <#= appName #>.Data { public class AppDbContext : DbContext { public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } <# foreach (string tableName in tableNames) { #> public DbSet<<#= tableName #>> <#= tableName #> { get; set; } <# } #> } } <# } #>
另外还可以生成Program.cs里的依赖注入代码片段,比如:
// 注册DbContext和Repository builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddScoped<ProductRepository>(); // 其他Repository注册...
- 动态命名空间:通过参数传入应用名称,自动生成对应命名空间(比如
YourApp.Models、YourApp.Controllers) - 处理主键类型:自动识别主键的数据类型,动态调整方法参数(比如
GetByIdAsync(Guid id)) - 添加异步支持:默认生成异步方法,符合ASP.NET Core的最佳实践
- 注释自动生成:给方法和属性添加XML注释,方便后续开发
- 过滤系统表:自动跳过sys开头的系统表,只生成用户自定义表的代码
- 依赖注入配置:生成Program.cs里的服务注册代码,减少手动配置的工作量
- 在Visual Studio里新建一个T4文件(右键项目 → 添加 → 新建项 → 搜索“Text Template”)
- 把上面的代码整合到.tt文件里,替换你的数据库连接字符串和应用名称
- 右键T4文件,选择“Run Custom Tool”,VS就会自动生成所有对应的代码文件
- 如果需要批量生成多个表的代码,模板会自动遍历数据库里的所有用户表,生成对应的Model、Repository和Controller
- 添加T4参数:在模板开头添加
<#@ parameter name="AppName" type="System.String" #>,这样可以在运行模板时传入参数,不用每次修改模板代码 - 生成DTO类:除了实体类,还可以生成用于API请求/响应的DTO,避免直接暴露数据库实体
- 处理外键关联:读取外键信息,自动生成实体的导航属性,以及关联的CRUD逻辑
- 自定义模板片段:把常用的代码片段抽成单独的T4模板文件,通过
<#@ include file="CommonTemplates.ttinclude" #>引用,方便维护
内容的提问来源于stack exchange,提问作者I Love Stackoverflow

