使用C#中的SQLCommand进行批量更新/插入

10
我如何使用SQLCommand实现批量update/insert。我想在C#的for循环中动态创建带有10个SQLParameter的MyObject[] SQLCommand文本。 对于大量的插入,我需要检查每条记录是否已经存在,即:
如果不存在(select pkid from table1 where fkid1=@fkid1 and fkid2=@fkid1) begin
insert....

end

这是从C#中完成的。数据库中没有存储过程。


我需要的是仅限于C#编写的代码,按照要求不允许使用SQL存储过程。 - MayureshP
5个回答

19
SqlCommand command = new SqlCommand();
// Set connection, etc.
for(int i=0; i< items.length; i++) {
    command.CommandText += string.Format("update mytable set s_id=@s_id{0} where id = @id{0};", i);
    command.Parameters.Add("@s_id" + i, items[i].SId);
    command.Parameters.Add("@id" + i, items[i].Id);
}
command.ExecuteNonQuery();

我澄清了问题,请您审核一下。 - MayureshP

10

编辑警告:尽管该答案在一定程度上正确,但并未解决所提出的问题,实际上ExecuteNonQuery会将工作负载提交到数据库(可以通过编写不正确的查询来证明:异常会在ExecuteNonQuery而不是Commit上引发)。

仅将所有CommandTexts追加到一个大的批处理命令中并不像看起来那么有用。

C#中准备好的语句的主要好处是,在创建命令时完成数据库中的工作负载。而不是在执行它时(例如使用ExecuteNonQuery()执行命令,仅当您没有创建事务对象时才执行它)。

为了避免这种情况,并且只在所有语句中为数据库创建工作负载一次,更好的方法是创建一个Transaction对象并提交此事务。然后,所有命令都将被执行,而无需在数据库中再进行任何工作负载。

这将是一个更好的方法:

// Try to create the Command as early as possible with a valid Connection object
string commandString = "UPDATE Mytable SET s_id=@s_id where id = @id;";
var command = new SqlCommand(commandString, connection);

// Then define a Transaction object with your Connection
var transaction = connection.BeginTransaction();
command.Transaction = transaction;

// Now iterate through your array
for(int i=0; i<array.Length; i++)
{
  command.Parameters.Add("@s_id", SqlDbType.YourType).Value = items[i].SId;
  command.Parameters.Add("@id", SqlDbType.YourType).Value = items[i].Id;
  command.ExecuteNonQuery(); // Not executed at this point
}

// And now execute it with the possibility to rollback all commands when it fails
try {  transaction.Commit(); } // Here the execution is committed to the DB
catch (Exception)
{
  transaction.Rollback();
  throw;
}

SQL语句在名为connectionString的变量中?什么鬼? - Evilripper
1
我已将“connectionString”名称更改为“commandString”。这应该是更好的命名方式,不是吗? - FluepkeSchaeng
如果我使用 executeScalar() 呢?返回值是什么? - bh_earth0
2
command.ExecuteNonQuery(); // Not executed at this point 这是不正确的。即使使用具有事务的连接执行,它也会被执行并发送到服务器。已使用 SQL Profiler 进行测试。 - Eakan Gopalakrishnan
除非我漏掉了什么,否则你应该在每次添加新参数时执行Parameters.Clear()。我的做法是在循环之前为每个参数添加一个Add,但不包括值,在循环内部设置参数值。 - Prethen
显示剩余2条评论

3

1
这是我测试批量插入的快速解决方案。
        using (var conn = new SqlConnection(GetConnectionStringFromSecret(args)))
        {
            conn.Open();

            stopwatch.Start();
            long counter = 0;
            var tran = conn.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted);
            SqlCommand cmd = new SqlCommand("", conn, tran);
            cmd.CommandType = System.Data.CommandType.Text;
            int batch_param_counter = 0;
            foreach (var chars_table in table_of_table_of_chars)
            {
                var key = string.Concat(chars_table);//get 1st param
                var hash = BitConverter.ToString(hasher.ComputeHash(Encoding.UTF8.GetBytes(key))).Replace("-", "").ToLowerInvariant();//get 2nd param

                cmd.CommandText += $"insert into hash_table([key], hash) values(@key{batch_param_counter}, @hash{batch_param_counter});{Environment.NewLine}";
                var param_key = new SqlParameter("@key" + batch_param_counter, System.Data.SqlDbType.VarChar, 20);
                param_key.Value = key;
                cmd.Parameters.Add(param_key);
                var hash_key = new SqlParameter("@hash" + batch_param_counter, System.Data.SqlDbType.VarChar, 32);
                hash_key.Value = hash;
                cmd.Parameters.Add(hash_key);
                batch_param_counter++;

                if (counter % 200 == 0)
                {
                    cmd.Prepare();
                    cmd.ExecuteNonQuery();
                    cmd.Dispose();
                    cmd = new SqlCommand("", conn, tran);
                    cmd.CommandType = System.Data.CommandType.Text;
                    batch_param_counter = 0;
                }

                if (counter % 20000 == 0)
                {
                    if (cmd != null && !string.IsNullOrEmpty(cmd.CommandText))
                    {
                        cmd.Prepare();
                        cmd.ExecuteNonQuery();
                        cmd.Dispose();
                        cmd = new SqlCommand("", conn, tran);
                        cmd.CommandType = System.Data.CommandType.Text;
                        batch_param_counter = 0;
                    }
                    tran.Commit();
                    tran = null;

                    if (Console.KeyAvailable)
                        break;

                    cmd.Transaction = tran = conn.BeginTransaction(System.Data.IsolationLevel.ReadUncommitted);
                }
                counter++;
            }

            if (cmd != null && !string.IsNullOrEmpty(cmd.CommandText))
            {
                cmd.Prepare();
                cmd.ExecuteNonQuery();
                cmd.Dispose();
            }
            if (tran != null)
                tran.Commit();

            stopwatch.Stop();
        }

每200个项目批量插入。 - Andrzej Pauli

0

您可以将值作为逗号分隔的字符串发送到存储过程参数中,然后使用 Split() SQL 函数拆分它们。

尝试一下

    StringBuilder Par1 = new StringBuilder("");
    StringBuilder Par2 = new StringBuilder("");
    for (var i = 0; i < MyObject.Length; i++)
    {
        if (i > 0)
        {
            Par1.Append(",");
            Par2.Append(",");
        }
        Par1.Append(MyObject[i].Prop1);
        Par2.Append(MyObject[i].Prop2);
    }
    myCommand.Parameters.Add(new SqlParameter("@Par1", Par1.ToString()));
    myCommand.Parameters.Add(new SqlParameter("@Par2", Par2.ToString()));
    myCommand.CommandText = "MyStoredProcedure";
    myCommand.CommandType = System.Data.CommandType.StoredProcedure;
    myCommand.ExecuteNonQuery();

你的存储过程将会长成这个样子

CREATE Procedure MyStoredProcedure
(
    @Par1 as varchar(max),
    @Par2 as varchar(Max)
)
AS
create table #AllValues
 (  
value1 varchar(50),
value2 varchar(50)
  );

 create table #Par1s
 (  
id integer, 
data varchar(50)
 );

 create table #Par2s
 (  
id integer, 
data varchar(50)
 );
 insert into #Par1s select * from dbo.Split (@Par1,',')
 insert into #Par2s select * from dbo.Split (@Par2,',')

Insert into #AllValues(value1,value2)
Select #Par1s.data,#Par2s.data From #Par1s Inner Join #Par2s On #Par1s.ID = #Par2s.ID


Insert into myTable(Col1,Col2)
Select value1,value2 from #AllValues

您可以使用相同的方法在InsertUpdate中传递任意数量的参数。


所有的工作都是用C#完成的,根据要求不允许使用存储过程。 - MayureshP
我不明白“没有存储过程角色”,但无论如何,为什么不使用比在代码中编写SQL语句更高效的“Linq to SQL”或“Entity Framework”呢? - Amir Ismail
该项目处于维护阶段。我们不能在其中实现新功能(只能进行修正)。 - MayureshP

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接