如何用C# ADO.NET生成含修改字段的T-SQL建表插入脚本?
优化你的ADO.NET数据脚本生成程序
嘿,你的需求很明确——从数据库提取Person表数据,脱敏姓名后生成包含建表和插入语句的SQL脚本。原代码有不少可以打磨的地方,比如资源管理、动态适配表结构、更高效的字符串处理等,我来给你梳理几个更巧妙的实现思路:
核心优化方向
- 动态获取表结构,告别硬编码:不用手动写CREATE TABLE语句,通过
DataReader.GetSchemaTable()自动读取列名和数据类型,表结构变化时代码不用跟着改。 - using语句自动释放资源:把SqlConnection、SqlCommand、SqlDataReader这些资源都放进using块里,系统会自动帮你释放,避免内存泄漏。
- 拆分逻辑提升可读性:把建表语句生成、插入语句生成拆成独立的方法,代码结构更清晰,后续维护也方便。
- 充分发挥StringBuilder的作用:原代码里StringBuilder基本没用到,要把所有SQL内容都追加进去,避免频繁字符串拼接的性能损耗。
改进后的完整代码
using System; using System.Data; using System.Data.SqlClient; using System.Text; class Program { static void Main(string[] args) { string conString = @"Data Source=.;Initial Catalog=TEST;Integrated Security=True"; string tableName = "Person"; string outputPath = @"script.txt"; var sqlBuilder = new StringBuilder(); using (var con = new SqlConnection(conString)) { con.Open(); // 生成CREATE TABLE语句 GenerateCreateTableScript(con, tableName, sqlBuilder); // 生成脱敏后的INSERT INTO语句 GenerateInsertScripts(con, tableName, sqlBuilder); } // 将生成的SQL写入文件 using (var writer = new System.IO.StreamWriter(outputPath)) { writer.Write(sqlBuilder.ToString()); } Console.WriteLine($"SQL脚本已生成到:{outputPath}"); Console.Read(); } private static void GenerateCreateTableScript(SqlConnection con, string tableName, StringBuilder builder) { var schemaCmd = new SqlCommand($"SELECT TOP 0 * FROM {tableName}", con); using (var schemaReader = schemaCmd.ExecuteReader(CommandBehavior.SchemaOnly)) { var schemaTable = schemaReader.GetSchemaTable(); builder.AppendLine($"CREATE TABLE {tableName} ("); var columnDefinitions = new StringBuilder(); foreach (DataRow row in schemaTable.Rows) { string columnName = row["ColumnName"].ToString(); string dataType = row["DataTypeName"].ToString(); int maxLength = row["ColumnSize"] != DBNull.Value ? Convert.ToInt32(row["ColumnSize"]) : 0; bool isNullable = Convert.ToBoolean(row["AllowDBNull"]); // 拼接列定义,比如 [Name] VARCHAR(20) NOT NULL string columnDef = $" [{columnName}] {dataType}"; if (maxLength > 0 && (dataType.Equals("varchar", StringComparison.OrdinalIgnoreCase) || dataType.Equals("nvarchar", StringComparison.OrdinalIgnoreCase))) { columnDef += $"({maxLength})"; } columnDef += isNullable ? " NULL," : " NOT NULL,"; columnDefinitions.AppendLine(columnDef); } // 移除最后一个多余的逗号,避免SQL语法错误 builder.Append(columnDefinitions.ToString().TrimEnd(',')); builder.AppendLine("\n);\n"); } } private static void GenerateInsertScripts(SqlConnection con, string tableName, StringBuilder builder) { var selectCmd = new SqlCommand($"SELECT * FROM {tableName}", con); using (var dataReader = selectCmd.ExecuteReader()) { if (!dataReader.HasRows) return; // 拼接INSERT语句的列名部分 var columnNames = new StringBuilder(); for (int i = 0; i < dataReader.FieldCount; i++) { columnNames.Append($"[{dataReader.GetName(i)}],"); } string columnsPart = columnNames.ToString().TrimEnd(','); builder.AppendLine($"INSERT INTO {tableName} ({columnsPart}) VALUES"); var valuesLines = new StringBuilder(); while (dataReader.Read()) { var values = new StringBuilder(); for (int i = 0; i < dataReader.FieldCount; i++) { string columnName = dataReader.GetName(i); object value = dataReader[i]; // 对姓名字段进行脱敏 if (columnName.Equals("name", StringComparison.OrdinalIgnoreCase) || columnName.Equals("surname", StringComparison.OrdinalIgnoreCase)) { values.Append("'xxx',"); } else if (value == DBNull.Value) { values.Append("NULL,"); } else if (value is string || value is char) { // 转义字符串中的单引号,避免SQL语法错误 string escapedValue = value.ToString().Replace("'", "''"); values.Append($"'{escapedValue}',"); } else { // 数字、日期等类型直接输出值 values.Append($"{value},"); } } string valuesPart = values.ToString().TrimEnd(','); valuesLines.AppendLine($" ({valuesPart}),"); } // 移除最后一个逗号并添加分号,完成INSERT语句 builder.Append(valuesLines.ToString().TrimEnd(',')); builder.AppendLine(";"); } } }
额外实用小技巧
- 脱敏字段配置化:可以把需要脱敏的字段名放到一个数组里,比如
string[] sensitiveFields = { "name", "surname" };,后续新增脱敏字段直接修改数组即可,不用改业务逻辑。 - SQL注入防护:如果后续要支持用户输入表名,一定要加白名单验证,避免SQL注入风险(当前代码用固定表名,暂时没问题)。
- 脚本格式化:通过缩进让生成的SQL脚本更易读,方便后续手动查看或直接执行。
内容的提问来源于stack exchange,提问作者mahiruzun




