1

I'm fairly new to SQL and trying to figure out the best way to add some predefined data. I figured out from searching around here that I should used a parameterized command to avoid a sql injection attack which isn't a huge concern in this case but I would like to avoid the possibility and learn to do it right... Anyway here is the code I have right now:

        using (SqlTransaction trans = connection.BeginTransaction())
        {
            foreach (IEnumerable<string> row in table.RowData)
            {
                using (SqlCommand sql = new SqlCommand("INSERT INTO " + table.Title
                    + " (" + string.Join(", ", table.Headers)
                    + ") VALUES (" + string.Join(", ", table.Headers.Select(x => "@" + x)) + ");", connection, trans))
                {

                    for (int i = 0; i < table.Headers.Count(); i++)
                    {
                        if (string.IsNullOrEmpty(row.ElementAt(i)))
                        { sql.Parameters.AddWithValue("@" + table.Headers.ElementAt(i), DBNull.Value); }
                        else
                        { sql.Parameters.AddWithValue("@" + table.Headers.ElementAt(i), row.ElementAt(i)); }
                    }
                    sql.ExecuteNonQuery();
                }
            }
            trans.Commit();
        }

This seems to work and all the data gets in there but it 'feels' inefficient to me. I'm wrapping it in a transaction so there is only one commit, but it's creating the parameters every time and just setting different values for each row.

Is there a way to make this use the same parameters but just set different values per row? Or is this the best way to do this and I should not worry about it?

Thanks in advance for any help you can give.

9
  • 2
    Objects in c# are cheap. Could it be more efficient? Possibly. But you aren't going to gain much of anything by reusing a parameter, and you might even introduce some subtle bugs by doing so. I advise against it. Commented Oct 28, 2016 at 18:16
  • With some more context we could help better, because we don't know whether this is supposed to be a reusable wrapper around the SQL APIs, a specific query with lots of insert statements and values, or what - the answer depends. Commented Oct 28, 2016 at 18:18
  • 3
    Sorry to have to tell you this, but this is open to injection attack -- you are concatenating variable strings (table.Title) into your query. Commented Oct 28, 2016 at 18:18
  • Also, I don't see any point in wrapping in a transaction if you have no code that does a rollback. Just more likely to have dead locks if you do it like this. Commented Oct 28, 2016 at 18:22
  • 2
    No, you can't use parameters for table and column names, only values. Commented Oct 28, 2016 at 18:28

3 Answers 3

2

We can do what you want by parsing the headers into parameters in a pre-processing step. I have also removed the explicit transaction because every single insert already gets an implicit transaction by default (why pay the performance penalty of two transactions?).

using (var command = new SqlCommand()) {
    command.CommandText =
        "INSERT INTO " + table.Title + " ("
      + string.Join(", ", table.Headers)
      + ") VALUES ("
      + string.Join(", ", table.Headers.Select(x => "@" + x))
      + ");";
    command.Connection = connection;

    foreach (var header in table.Headers) {
        /*
             Add all parameters as strings. One could choose to infer the
             data types by inspecting the first N rows or by using some sort
             of specification to map the types from A to B.
         */
        command.Parameters.Add("@" + header, typeof(string));
    }

    foreach (var row in table.RowData) {
        for (var i = 0; i < table.Headers.Count(); i++) {
            if (!string.IsNullOrEmpty(row.ElementAt(i))) {
                command.Parameters["@" + table.Headers.ElementAt(i)].Value = row.ElementAt(i);
            }
            else {
                command.Parameters["@" + table.Headers.ElementAt(i)].Value = DBNull.Value;
            }
        }

        command.ExecuteNonQuery();
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

Ok I get that, except that command.Parameters.Add() requires a second parameter of the data type... Not too hard to do but it's nice that AddWithValue seems to automatically recognize the data type...
@sfaust It does, based on the incoming value. Since we're hoisting the logic up you'll have to determine the type ahead of time. One way is to infer the data type using the first n rows.
Yes not too hard to do, just wanted to point it out mainly for anyone else that comes along with this question. I will modify a bit to get the data types but thanks, that's what I was looking for!
Also, it should be command.Parameters[...].Value = ... You don't have value in there. Can I request you update the answer with that and the data type so it's not confusing to other newbies? :)
@sfaust Of course. My apologies, I was writing this without testing it.
|
0

this is my example of insert that works for me

  private void insertWordCount(string songId, string wordId, int wordCount)
    {
        string query = "insert into songs_words_conn values(@wordId,@songId,@wordCount)";
        SqlCommand cmd = new SqlCommand(query, conn);

        cmd.Parameters.AddWithValue("@wordId", wordId);
        cmd.Parameters.AddWithValue("@songId", songId);
        cmd.Parameters.AddWithValue("@wordCount", wordCount);

        cmd.ExecuteNonQuery();
    }

1 Comment

That does work and is essentially what I'm doing. However in order to add multiple values to the table it would have to loop which creates the parameters multiple times which is what I was trying to avoid...
0

Yes, you can be much more effecient by reusing SqlParameter objects. Here is some pseudo code:

        const string sql = "INSERT INTO table1 (column1) VALUES (@p0)";
        using (var sqlCommand = new SqlCommand(sql, connection, transaction))
        {
            var param1 = sqlCommand.Parameters.Add("@p0", SqlDbType.Int);
            foreach (var row in table)
            {
                param1.Value = row["value"];
                sqlCommand.ExecuteNonQuery();
            }
        }

3 Comments

This is doing something different. This is only adding one column per row. How exactly is this an answer?
This shows you HOW to reuse SQL Parameters in a foreach, which was the gist of the question. Using a little critical thinking will get the user to the complete answer.
Not horrible at all, @jhilden is constructing the parameter outside the foreach loop where I'm constructing it inside the loop. The original code is creating new ones every time, this code is not. How is that horrible?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.