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

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

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

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

谢谢!

【问题讨论】:

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

标签: c# sql-server azure-functions azure-sql-database prepared-statement


【解决方案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) 存在冲突问题,由于某种原因,这是我的加密列。它们在表类型中的定义与我尝试插入的表中的定义相同,因此我不太确定存在什么问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-19
    • 1970-01-01
    • 2019-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多