20

I have a huge list of INSERT INTO ... strings. Currently I run them with:

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    foreach (var commandString in sqlCommandList)
    {
        SqlCommand command = new SqlCommand(commandString, connection);
        command.ExecuteNonQuery();
    }
}

I see that each ExecuteNonQuery() also executes commit.

  1. Is there a way to insert all rows in a single transaction (commit in the end)?
  2. The reason I want a single transaction is to make my "inserts" process faster. Will a single transaction also make it quicker?
Bick
  • 17,833
  • 52
  • 146
  • 251
  • 2
    SQlBulkCopy http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopy.aspx – Lorentz Vedeler Jun 09 '13 at 11:00
  • you could use an transaction scope (refer to MSDN). – TGlatzer Jun 09 '13 at 11:03
  • You can use parallel for each – Shyam sundar shah Jun 09 '13 at 11:30
  • 1
    @Shyamsundarshah: 1. parallel.foreach loads every transaction on a different thread (each thread will end with a commit and thus there will be the same amount of commits). 2. I tried it but I had a problem with the order of the inserts. some inserts occured before other and broke my foreign key restriction. But thanks :-) – Bick Jun 10 '13 at 11:30

6 Answers6

43

Its recommended to use SQL transaction in case you are executing Multiple queries in one thread , you can have it like this :

    SqlTransaction trans; 

    try
    {
        SqlConnection connection = new SqlConnection(connectionString);
        connection.Open();

        trans = connection.BeginTransaction(); 

        foreach (var commandString in sqlCommandList)
        {
            SqlCommand command = new SqlCommand(commandString, connection,trans);
            command.ExecuteNonQuery();
        }

        trans.Commit(); 
    }
    catch (Exception ex) //error occurred
    {
        trans.Rollback();
        //Handel error
    }
Mohammad abumazen
  • 1,286
  • 1
  • 11
  • 24
  • "ExecuteNonQuery requires the command to have a transaction when the connection assigned to the command is in a pending local transaction. The Transaction property of the command has not been initialized." – Fandango68 Mar 03 '16 at 06:15
  • 2
    @Fernando68 >>> SqlCommand command = new SqlCommand(commandString, connection,trans); – Mohammad abumazen Mar 06 '16 at 09:30
  • Don't forget to dispose the SqlTransaction and SqlConnection objects when you are done using them. – Sal Jan 24 '17 at 18:16
  • 3
    Danger Zone! No `using` means leaking resources. – JoeBrockhaus Sep 19 '18 at 16:31
15

You might probably gain some performance by using just one single transaction and command, as follows:

using (SqlConnection connection = new SqlConnection(connectionString))
{
   try
   {
      connection.Open();

      using (SqlTransaction trans = connection.BeginTransaction())
      {
          using (SqlCommand command = new SqlCommand("", connection,trans))
          {
             command.CommandType = System.Data.CommandType.Text;

             foreach (var commandString in sqlCommandList)
             {
                command.CommandText = commandString;
                command.ExecuteNonQuery();
             }
          }

          trans.Commit();
       }        
    }
    catch (Exception ex) //error occurred
   {
       //Handel error
   }
}
Gerardo H
  • 694
  • 6
  • 9
  • 1
    I would put the `command.ExecuteNonQuery();` line in its own `try catch` block so I could call `trans.Rollback();` explicitly if an error occurred. That's the way that the MS docs show it, at any rate. – Tony Vitabile Oct 02 '13 at 16:26
  • 8
    @Tony There is no need of calling trans.rollback. SqlTransaction object will roll back in its Dispose() method if it was not explicitly committed (e.g. if an exception is thrown). See following thread: [Why use a using statement with a SqlTransaction?](http://stackoverflow.com/questions/1127830/why-use-a-using-statement-with-a-sqltransaction) – Gerardo H Oct 10 '13 at 01:38
  • 1
    @Gerardo H - However, if you call it explicitly, 1) you are sure it is done should `Dispose` ever stop calling rollback for you, 2) it is clear to the next developer when a rollback occurs. 3) The catch block may also allow for some specific error logging. Whether those make it worth the extra clutter will vary from case to case. I've used it both ways. – xr280xr Apr 20 '16 at 22:13
  • Yes, it will work, just have in mind taking care of adding any parameter(s) which must be provided to the SPs. – Gerardo H May 17 '18 at 16:40
0

A little late, but if you are inserting all of the values into the same table, code the SQL insert as "insert into tablex (f1, f2, f3,...) values (@F1,@F2,@F3...)". Create the command and add the parameters @F1..., and then set the Prepare flag on the command. Now as you loop through your list of values to insert, you can set them into the appropriate parameters and then do the ExecuteNonQuery. SQL will pre-parse the command string once, and then use the new parameters each time. This is a bit faster.

Finally, you can execute multiple SQL statements in a single command by appending ';' to each statement, if you must execute the entire string. You can bunch a number of these commands together and make one request to SQL server to execute them.

Arkitec
  • 333
  • 3
  • 17
0

You can just concatenate the sql and let the server handle it:

using (SqlConnection connection = new SqlConnection(connectionString))
{
    string lsSql = string.Empty;
    foreach (var commandString in sqlCommandList)
    {
        lsSql = lsSql + commandString + " ; " + Environment.NewLine;
    }

    connection.Open();
    SqlCommand command = new SqlCommand(lsSql, connection);
    command.ExecuteNonQuery();
}
Arjan
  • 1
  • 2
    Does this really mean all the statements are run in the same transaction? If so, can you point me to where documentation says so, or provide evidence? – Mattias Nordqvist Feb 04 '20 at 10:09
0

Here is what I use on my daily work, before it a use a foreach for any non-query that I need to run on database. You can see that I'm using the OracleCommand, but if you need you can change to SQL statement

    public static void ExecuteDatabaseNonQuery(string command)
    {
        OracleCommand cmd = new OracleCommand();
        cmd.Connection = conn;

        OracleTransaction transaction;
        transaction = conn.BeginTransaction(IsolationLevel.ReadCommitted);
        cmd.Transaction = transaction;
        try
        {
            cmd.CommandText = command;
            var update = cmd.ExecuteNonQuery();
            transaction.Commit();

            Console.WriteLine("{0} rows updated", update);
        }
        catch (Exception e)
        {
            transaction.Rollback();
            throw new Exception("Error: " + e);
        }
    }

Note: If theres any uncommited changes on database this method will wait indefinitely

Tab
  • 23
  • 5
-2

You can use Parallel for each

   using (SqlConnection connection = new SqlConnection(connectionString))
    {
        List<string> sqlCommandList = new List<string>();
        connection.Open();
        Parallel.ForEach(sqlCommandList, commandString =>
        {
            SqlCommand command = new SqlCommand(commandString, connection);
            command.ExecuteNonQuery();
        });
    }
Shyam sundar shah
  • 2,473
  • 1
  • 25
  • 40
  • 3
    1. parallel.foreach loads every transaction on a different thread (each thread will end with a commit and thus there will be the same amount of commits). 2. I tried it but I had a problem with the order of the inserts. some inserts occured before other and broke my foreign key restriction. But thanks :-) – Bick Jun 10 '13 at 11:32