【问题标题】:How to use SqlCommand to CREATE DATABASE with parameterized db name?如何使用 SqlCommand 创建具有参数化数据库名称的数据库?
【发布时间】:2011-04-01 05:45:52
【问题描述】:

简而言之。我有两个简单的助手:

    private SqlCommand CreateCommand(string text)
    {
        SqlCommand cmd = new SqlCommand();
        cmd.Connection = connection;
        cmd.CommandType = CommandType.Text;
        cmd.CommandText = text;
        return cmd;
    }

    void SetParameter(SqlCommand cmd, string p, string dbName)
    {
        cmd.Parameters.Add(p, SqlDbType.NVarChar);
        cmd.Parameters[p].Value = dbName;
    }

这执行正常:

var cmd = CreateCommand("CREATE DATABASE Demo "+
            @"ON (FILENAME = N'c:\demo_data.mdf') "+ 
            @"LOG ON (FILENAME = N'c:\demo_data.mdf.LDF') "+
            "FOR ATTACH " +
            "GO");
cmd.ExecuteNonQuery();

但这不是:

string dataBaseAttachText = "CREATE DATABASE @dbname " +
                              "ON (FILENAME = @filename) " +
                              "LOG ON (FILENAME = @filenamelog) " +
                              "FOR ATTACH GO";
var cmd = CreateCommand(dataBaseAttachText);

SetParameter(cmd, "@dbname", "Demo");
SetParameter(cmd, "@filename", @"c:\demo_data.mdf");
SetParameter(cmd, "@filenamelog", @"c:\demo_data.mdf.LDF");

cmd.ExecuteNonQuery();

为什么?

【问题讨论】:

标签: c# sql-server ado.net


【解决方案1】:

我通过在存储的 precedure 'sp_executesql' 中调用构建解决了这个任务。 用于创建 DB 的连接字符串指向“master”。 完整的 SQL 语句是参数值的一部分:

using (SqlConnection connection = new SqlConnection(ConnectionString))
{
    using (SqlCommand command = new SqlCommand("sp_executesql", connection))
    {
        command.CommandType = CommandType.StoredProcedure;
        var sql = $"CREATE DATABASE NewDatabaseName";
        command.Parameters.Add("MyParameterName", SqlDbType.NVarChar).Value = sql;
        connection.Open();
        command.ExecuteNonQuery();
    }
}

【讨论】:

    【解决方案2】:

    我通过创建一个扩展方法来适当地包装所有实体来解决这个问题。

        /// <summary>
        /// Quotes the provided string in a sql friendly way using the standard [ and ] characters 
        /// </summary>
        /// <param name="ObjectName">string to quote</param>
        /// <example>
        /// "mytable".QuoteSqlName() would return [mytable] 
        /// "my[complex]table".QuoteSqlName()  would return [my[[complex]]table]
        /// </example>
        /// <returns>quoted string wrapped by quoting characters</returns>
        /// <remarks>For dynamic sql this may need to be called multiple times, one for each level of encapsulation.</remarks>
        public static string QuoteSqlName(this string ObjectName)
        {
            return ObjectName.QuoteSqlName(']');
        }
    
        /// <summary>
        /// Quotes the provided string in a sql friendly way using the provided character
        /// </summary>
        /// <param name="ObjectName">string to quote</param>
        /// <param name="QuoteCharacter">Character to quote with, use [ or ] for standard sql quoting</param>
        /// <example>
        /// "mytable".QuoteSqlName() would return [mytable] 
        /// "my[complex]table".QuoteSqlName()  would return [my[[complex]]table]
        /// "justin's computer".QuoteSqlName('\'') would return 'justin''s computer'
        /// </example>
        /// <returns>quoted string wrapped by quoting characters</returns>
        public static string QuoteSqlName(this string ObjectName, char QuoteCharacter)
        {
            return ObjectName.QuoteSqlName(QuoteCharacter, false);
        }
    
        /// <summary>
        /// Quotes the provided string in a sql friendly way using the provided character
        /// </summary>
        /// <param name="ObjectName">string to quote</param>
        /// <param name="QuoteCharacter">Character to quote with, use [ or ] for standard sql quoting</param>
        /// <param name="IsNvarChar">if true and QuoteCharacter is ' will prefix the quote with N e.g. N'mytable' vs 'mytable'</param>
        /// <example>
        /// "mytable".QuoteSqlName() would return [mytable] 
        /// "my[complex]table".QuoteSqlName()  would return [my[[complex]]table]
        /// "justin's computer".QuoteSqlName('\'') would return 'justin''s computer'
        /// "mytable".QuoteSqlName('\'',false) would reutrn 'mytable'
        /// "mytable".QuoteSqlName('[',true) would return [mytable]
        /// "mytable".QuoteSqlName('\'',true) would reutrn N'mytable'
        /// </example>
        /// <returns>quoted string wrapped by quoting characters</returns>
        public static string QuoteSqlName(this string ObjectName, char QuoteCharacter, bool IsNvarChar)
        {
            if (string.IsNullOrEmpty(ObjectName))
                return ObjectName;
    
            char OtherQuoteCharacter = (char)0;
            bool UseOtherChar = false;
            if (QuoteCharacter == ']' || QuoteCharacter == '[')
            {
                QuoteCharacter = '[';
                OtherQuoteCharacter = ']';
                UseOtherChar = true;
            }
    
            var sb = new StringBuilder((int)(ObjectName.Length * 1.5) + 2);
            if (QuoteCharacter == '\'' && IsNvarChar)
                sb.Append('N');
    
            sb.Append(QuoteCharacter); // start with initial quote character
            for (var i = 0; i < ObjectName.Length; i++)
            {
                sb.Append(ObjectName[i]);
                // if its a quote character, add it again e.g. ] becomes ]]
                if (ObjectName[i] == QuoteCharacter || UseOtherChar && ObjectName[i] == OtherQuoteCharacter)
                    sb.Append(ObjectName[i]);
            }
            sb.Append(UseOtherChar ? OtherQuoteCharacter : QuoteCharacter); // finish with other final quote character
    
            return sb.ToString();
        }
    

    用法:

    var QuotedDBName = this.DBName.QuoteSqlName();
    CreateDBQuery.AppendFormat("USE {0};", QuotedDBName);
    CreateDBQuery.AppendFormat("IF TYPE_ID({0}) IS NULL", DBType.Name.QuoteSqlName('\'', true));
    CreateDBQuery.AppendFormat("    CREATE TYPE {0} as {1};", DBType.Name.QuoteSqlName(), DBType.Value);
    

    【讨论】:

      【解决方案3】:

      作为丹尼尔和里奇的答案的一点组合。通过对 sp_executesql 运行 DML 查询,您可以获得动态构建的查询,也可以通过使用 QUOTENAME 来避免任何可能有人传入的 sql 注入尝试。

      string dataBaseAttachText = @"
      DECLARE @SQLString nvarchar(500);
      DECLARE @ParmDefinition nvarchar(500);
      SET @SQLString =
           N'CREATE DATABASE ' + QUOTENAME(@dbName) + N' 
             ON (FILENAME = @filename) 
             LOG ON (FILENAME = @filenamelog) 
             FOR ATTACH GO'
      SET ParmDefinition = N'@filename nvarchar(MAX), @filenamelog nvarchar(MAX)'
      EXECUTE sp_executesql @SQLString, @ParmDefinition, @filename = @filename, @filenamelog = @filenamelog";
      
      var cmd = CreateCommand(dataBaseAttachText); 
      
      SetParameter(cmd, "@dbname", "Demo");
      SetParameter(cmd, "@filename", @"c:\demo_data.mdf"); 
      SetParameter(cmd, "@filenamelog", @"c:\demo_data.ldf"); 
      
      cmd.ExecuteNonQuery(); 
      

      这应该执行以下 DML sql 查询并传递适当的参数。

      CREATE DATABASE [Demo]
             ON (FILENAME = @filename) 
             LOG ON (FILENAME = @filenamelog) 
             FOR ATTACH GO
      

      【讨论】:

      • CreateCommand 属于哪个程序集? .net 45 中是否可用
      • @user1591131 CreateComand 是写在本页顶部问题中的函数。
      【解决方案4】:

      遗憾的是,您可以通过将 DDL 操作包装在 DML 操作中来完成此操作。

      var createDatabaseQuery = "exec ('CREATE DATABASE ' + @databaseName)";
      
      var sqlCommand = new SqlCommand(createDatabaseQuery, sqlConnection);
      sqlCommand.Parameters.Add("@databaseName", SqlDbType.Text);
      sqlCommand.Parameters["@databaseName"].Value = "HelloWorld";
      
      sqlCommand.ExecuteNonQuery();
      

      【讨论】:

      • 虽然看起来这种方法可以防止 SQL 注入(使用参数可以),但它没有!
      【解决方案5】:

      DML 操作支持参数而不是 DDL 操作,没有 DDL 操作的执行计划。您将需要使用动态 SQL

      DDL = 数据定义语言(创建、删除、更改....)

      DML = 数据操作语言(选择、更新、删除、插入)

      【讨论】:

      • 不是这个问题的真正答案,Rich Hildebrand 有更好的答案。
      【解决方案6】:

      您只能在 SQL Server 支持的地方使用参数。不幸的是,SQL Server 不支持参数化的CREATE DATABASE 语句(虽然我感觉文件名部分可能支持参数)。

      您需要自己构建 SQL:

      string dataBaseAttachText = "CREATE DATABASE [" + dbName + "] " + 
                                    "ON (FILENAME = @filename) " + 
                                    "LOG ON (FILENAME = @filenamelog) " + 
                                    "FOR ATTACH GO"; 
      var cmd = CreateCommand(dataBaseAttachText); 
      
      SetParameter(cmd, "@filename", @"c:\demo_data.mdf"); 
      SetParameter(cmd, "@filenamelog", @"c:\demo_data.mdf.LDF"); 
      
      cmd.ExecuteNonQuery(); 
      

      注意:这很容易受到 SQL 注入攻击,因此必须小心;如果您不信任数据库名称的来源,请不要这样做!

      如果文件名部分也无法参数化,您需要对文件名部分进行类似更改。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-21
        • 2014-07-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多