如何在 C# 中创建动态参数化 SQL 查询字符串

Posted

技术标签:

【中文标题】如何在 C# 中创建动态参数化 SQL 查询字符串【英文标题】:How to create a Dynamic Parameterized SQL Query String in C# 【发布时间】:2022-01-06 20:24:05 【问题描述】:

我最终试图将存储在我的 Function App 内的并发列表中的一些记录保存到带有参数化值的 SQL Server 数据库中,其中一个是加密列(不确定是否相关)。

目前,我正在遍历每条记录并提取值、参数化它们并将它们保存到数据库中;为每条记录执行此操作。不过,据我了解,这是非常低效的,我被告知使用每条记录的参数化值创建一个字符串,然后将该单个字符串作为 SQL Server 查询运行会更有效。

有人可以向我解释一下我是如何实现这样的目标的吗?或者我是否弄错了?

谢谢!

【问题讨论】:

使用表值参数一次性发送多条记录。 表值参数是我的首选,但对于 2016+,我倾向于选择传递 JSON。它提供了更多的灵活性。 【参考方案1】:

以防万一它对将来的任何人有所帮助,我最终解决了这个问题。

使用 SqlBulkCopy 的原因是因为单独的 TVP 似乎与保存到包含 Always Encrypted 列的 SQL 表不兼容。尽管类型定义相同,但我收到了一些奇怪的操作数冲突错误。 DataTable 和 TVP 似乎不能很好地处理加密列。

将 SqlBulkCopy 与 TVP 一起使用似乎是一种解决方法,它允许我们将 C# 脚本中的数据保存到具有始终加密列的 SQL 表中,将 TVP 用于单个查询语句以加快处理时间。

using (SqlConnection connection = new SqlConnection(<connection_string>))

    connection.AccessToken = new VisualStudioCredential().GetToken()
    if (connection.State == ConnectionState.Closed)
        connection.Open();

    // Create a new DataTable - !Important - We also have to Create Table Type in our Database as mentioned in this thread.
    DataTable saveBatchTable = new DataTable();

    // Add rows to the DataTable
    saveBatchTable.Columns.Add("col_1", typeof(string));
    saveBatchTable.Columns.Add("col_2", typeof(long));
    saveBatchTable.Columns.Add("col_3", typeof(bool));

    // Assuming we have a list of records - for each 'record' in our list...
    foreach (custObject record in recordList)
    
        // and for each index attribute value within each record object...
        foreach (long idx in record.index)
        
            // Add a row of values corresponding to the columns previously added
            saveBatchTable.Rows.Add(record.col1Val, record.col2Val, true)
        
    

    saveBatchTable.AcceptChanges();

    string stagingTableName = "MyStagingTable"

    // Create an empty temporary table with the headers from your Source  Table within your DB
    using (var cmd = new SqlCommand("SELECT * INTO [" + stagingTableName + "] FROM [<SourceTableName>] WHERE 1 = 2;", connection))
    
        cmd.ExecuteNonQuery();
    

    // Write the DataTable (saveBatchTable) to the new temporary table
    using (var bulkCopy = new SqlBulkCopy(connection))
    
        bulkCopy.DesintationTableName = "[" + stagingTableName + "]";
        bulkCopy.WriteToServer(saveBatchTable);
       

    // Store the data within the temporary table to our Source Table then drop the temp table
    using (var cmd = new SqlCommand("BEGIN TRAN; INSERT [<SourceTableName>] SELECT * FROM [" + stagingTableName + "]; DROP TABLE [" + stagingTableName + "]; COMMIT", connection))
    
        Console.WriteLine("Number of Rows Affected = " +  cmd.ExecuteNonQuery().ToString());
    
    
    connection.Close();


【讨论】:

【参考方案2】:

[C#]

您可以使用Table-valued parameters 在单个 SQL 查询中发送多行。 流程将是

定义table type。架构将与要插入的参数相同。 使用与table type 完全相同的名称和类型创建一个DataTable。 在查询中将DataTable 作为参数传递。

样本

CREATE TYPE MyTableType AS TABLE
    ( mytext TEXT,
      num INT );
using (SqlConnection connection = new SqlConnection(CloudConfigurationManager.GetSetting("Sql.ConnectionString")))

    connection.Open();

    DataTable table = new DataTable();
    // Add columns and rows. The following is a simple example.
    table.Columns.Add("mytext", typeof(string));
    table.Columns.Add("num", typeof(int));
    for (var i = 0; i < 10; i++)
    
        table.Rows.Add(DateTime.Now.ToString(), DateTime.Now.Millisecond);
    

    SqlCommand cmd = new SqlCommand(
        "INSERT INTO MyTable(mytext, num) SELECT mytext, num FROM @TestTvp",
        connection);

    cmd.Parameters.Add(
        new SqlParameter()
        
            ParameterName = "@TestTvp",
            SqlDbType = SqlDbType.Structured,
            TypeName = "MyTableType",
            Value = table,
            Direction = ParameterDirection.Input,
        );

    cmd.ExecuteNonQuery();

参考:https://docs.microsoft.com/en-us/azure/azure-sql/performance-improve-use-batching#table-valued-parameters

[JAVA]

您可以使用PreparedStatement,创建一批要插入的行(ps.addBatch()),然后一次性插入一批(ps.executeBatch())。

示例:

PreparedStatement ps= con.prepareStatement("INSERT INTO Sample VALUES (?, ?, ?, ?)");

for(int i; i<10; i++)
  ps.setString(1, "String1");
  ps.setString(2, "String2");
  ps.setString(3, "String3");
  ps.setInt(4, 1000);

  ps.addBatch();


ps.executeBatch();

如果要插入的记录很多,您可以创建多个批次并将它们插入到循环本身中。

【讨论】:

@Dai 感谢您的指出。我现在已经更正了答案。 非常有帮助!非常感谢您花时间向我解释这一点! 如果我已经有一个现有的表,我正在尝试将这个新 DataTable 中的记录保存到其中,大概我不需要 CREATE TYPE MyTableType AS TABLE - 还是我弄错了?我是否必须根据要保存到的现有表创建新表类型?谢谢! @Klh 是的,您必须创建一个table type。值将存储在创建的table type 中,并将在插入语句中传递。请参考上面的示例并尝试创建示例。它会让你更清楚。 谢谢!当我意识到我需要创建一个新类型时,我昨天能够让它工作。 - 我只是在插入加密列值时遇到问题,因为当我只是使用参数化值时,我指定了 varchar(100) 或 varchar(200),但是,现在我已经在我的表类型中指定了这些但我与 varchar 和 varchar(100) 存在冲突问题,由于某种原因,这是我的加密列。它们在表类型中的定义与我尝试插入的表中的定义相同,因此我不太确定存在什么问题。

以上是关于如何在 C# 中创建动态参数化 SQL 查询字符串的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 C# 在 SQL Server 数据库中创建视图?

如何在不插入值的情况下在sql中创建动态行?

如何在 PHP 中为动态查询构建参数化 PDO 语句?

如何避免从 C# 构建的 Sql Server 2005 参数化查询变慢

如何在 postgres 中创建表并插入具有动态值的数据

在 PL/SQL 中创建动态对象