This way may not be faster than the stringbuilder approach, but it is parameterized:
/// <summary>
/// Bulk insert some data, uses parameters
/// </summary>
/// <param name="table">The Table Name</param>
/// <param name="inserts">Holds list of data to insert</param>
/// <param name="batchSize">executes the insert after batch lines</param>
/// <param name="progress">Progress reporting</param>
public void BulkInsert(string table, MySQLBulkInsertData inserts, int batchSize = 100, IProgress<double> progress = null)
{
if (inserts.Count <= 0) throw new ArgumentException("Nothing to Insert");
string insertcmd = string.Format("INSERT INTO `{0}` ({1}) VALUES ", table,
inserts.Fields.Select(p => p.FieldName).ToCSV());
StringBuilder sb = new StringBuilder();
using (MySqlConnection conn = new MySqlConnection(ConnectionString))
using (MySqlCommand sqlExecCommand = conn.CreateCommand())
{
conn.Open();
sb.AppendLine(insertcmd);
for (int i = 0; i < inserts.Count; i++)
{
sb.AppendLine(ToParameterCSV(inserts.Fields, i));
for (int j = 0; j < inserts[i].Count(); j++)
{
sqlExecCommand.Parameters.AddWithValue(string.Format("{0}{1}",inserts.Fields[j].FieldName,i), inserts[i][j]);
}
//commit if we are on the batch sizeor the last item
if (i > 0 && (i%batchSize == 0 || i == inserts.Count - 1))
{
sb.Append(";");
sqlExecCommand.CommandText = sb.ToString();
sqlExecCommand.ExecuteNonQuery();
//reset the stringBuilder
sb.Clear();
sb.AppendLine(insertcmd);
if (progress != null)
{
progress.Report((double)i/inserts.Count);
}
}
else
{
sb.Append(",");
}
}
}
}
This uses the helper classes as below:
/// <summary>
/// Helper class to builk insert data into a table
/// </summary>
public struct MySQLFieldDefinition
{
public MySQLFieldDefinition(string field, MySqlDbType type) : this()
{
FieldName = field;
ParameterType = type;
}
public string FieldName { get; private set; }
public MySqlDbType ParameterType { get; private set; }
}
///
///You need to ensure the fieldnames are in the same order as the object[] array
///
public class MySQLBulkInsertData : List<object[]>
{
public MySQLBulkInsertData(params MySQLFieldDefinition[] fieldnames)
{
Fields = fieldnames;
}
public MySQLFieldDefinition[] Fields { get; private set; }
}
And this helper method:
/// <summary>
/// Return a CSV string of the values in the list
/// </summary>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
private string ToParameterCSV(IEnumerable<MySQLFieldDefinition> p, int row)
{
string csv = p.Aggregate(string.Empty,
(current, i) => string.IsNullOrEmpty(current)
? string.Format("@{0}{1}",i.FieldName, row)
: string.Format("{0},@{2}{1}", current, row, i.FieldName));
return string.Format("({0})", csv);
}
Maybe not super elegant but it works well. I require Progress tracking so that is included for me, feel free to remove that part.
This will produce SQL commands similar to your desired output.
EDIT: ToCSV:
/// <summary>
/// Return a CSV string of the values in the list
/// </summary>
/// <param name="intValues"></param>
/// <param name="separator"></param>
/// <param name="encloser"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static string ToCSV<T>(this IEnumerable<T> intValues, string separator = ",", string encloser = "")
{
string result = String.Empty;
foreach (T value in intValues)
{
result = String.IsNullOrEmpty(result)
? string.Format("{1}{0}{1}", value, encloser)
: String.Format("{0}{1}{3}{2}{3}", result, separator, value, encloser);
}
return result;
}