You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用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

火山引擎 最新活动