TempTableHelper.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. using System;
  2. using System.Data;
  3. using System.Data.SqlClient;
  4. using System.Text.RegularExpressions;
  5. namespace JLHHJSvr.Tools
  6. {
  7. public static class TempTableHelper
  8. {
  9. private static readonly Regex TempTableNameRegex = new Regex(@"^#{1,2}[A-Za-z_][A-Za-z0-9_]*$", RegexOptions.Compiled);
  10. /// <summary>
  11. /// Creates a SQL Server temp table from DataTable columns and writes rows by SqlBulkCopy.
  12. /// </summary>
  13. public static void CreateAndBulkInsert(SqlCommand cmd, string tempTableName, DataTable dataTable, bool recreate = true, int batchSize = 1000, int bulkCopyTimeout = 60)
  14. {
  15. CheckArguments(cmd, tempTableName, dataTable);
  16. if (recreate)
  17. {
  18. DropIfExists(cmd, tempTableName);
  19. }
  20. CreateTempTable(cmd, tempTableName, dataTable);
  21. BulkInsert(cmd, tempTableName, dataTable, batchSize, bulkCopyTimeout);
  22. }
  23. /// <summary>
  24. /// Writes DataTable rows to an existing SQL Server temp table by SqlBulkCopy.
  25. /// </summary>
  26. public static void BulkInsert(SqlCommand cmd, string tempTableName, DataTable dataTable, int batchSize = 1000, int bulkCopyTimeout = 60)
  27. {
  28. CheckArguments(cmd, tempTableName, dataTable);
  29. using (var sqlBC = cmd.Transaction == null
  30. ? new SqlBulkCopy(cmd.Connection)
  31. : new SqlBulkCopy(cmd.Connection, SqlBulkCopyOptions.Default, cmd.Transaction))
  32. {
  33. sqlBC.BatchSize = batchSize;
  34. sqlBC.BulkCopyTimeout = bulkCopyTimeout;
  35. sqlBC.DestinationTableName = tempTableName;
  36. foreach (DataColumn column in dataTable.Columns)
  37. {
  38. sqlBC.ColumnMappings.Add(column.ColumnName, column.ColumnName);
  39. }
  40. sqlBC.WriteToServer(dataTable);
  41. }
  42. }
  43. /// <summary>
  44. /// Drops a SQL Server temp table in the current connection when it exists.
  45. /// </summary>
  46. public static void DropIfExists(SqlCommand cmd, string tempTableName)
  47. {
  48. CheckCommand(cmd);
  49. CheckTempTableName(tempTableName);
  50. cmd.CommandType = CommandType.Text;
  51. cmd.Parameters.Clear();
  52. cmd.CommandText = string.Format(
  53. "IF OBJECT_ID('tempdb..{0}') IS NOT NULL DROP TABLE {1}",
  54. tempTableName.Replace("'", "''"),
  55. QuoteName(tempTableName));
  56. cmd.ExecuteNonQuery();
  57. }
  58. private static void CreateTempTable(SqlCommand cmd, string tempTableName, DataTable dataTable)
  59. {
  60. if (dataTable.Columns.Count == 0)
  61. {
  62. throw new ArgumentException("DataTable must contain at least one column.", "dataTable");
  63. }
  64. string columnSql = string.Empty;
  65. foreach (DataColumn column in dataTable.Columns)
  66. {
  67. if (!string.IsNullOrEmpty(columnSql))
  68. {
  69. columnSql += ",";
  70. }
  71. columnSql += string.Format("{0} {1} {2}",
  72. QuoteName(column.ColumnName),
  73. GetSqlType(column),
  74. column.AllowDBNull ? "NULL" : "NOT NULL");
  75. }
  76. cmd.CommandType = CommandType.Text;
  77. cmd.Parameters.Clear();
  78. cmd.CommandText = string.Format("CREATE TABLE {0} ({1})", QuoteName(tempTableName), columnSql);
  79. cmd.ExecuteNonQuery();
  80. }
  81. private static string GetSqlType(DataColumn column)
  82. {
  83. if (column.ExtendedProperties.ContainsKey("SqlType"))
  84. {
  85. var sqlType = Convert.ToString(column.ExtendedProperties["SqlType"]);
  86. if (!string.IsNullOrWhiteSpace(sqlType))
  87. {
  88. return sqlType;
  89. }
  90. }
  91. var type = Nullable.GetUnderlyingType(column.DataType) ?? column.DataType;
  92. if (type == typeof(string))
  93. {
  94. return column.MaxLength > 0 && column.MaxLength <= 4000
  95. ? string.Format("NVARCHAR({0})", column.MaxLength)
  96. : "NVARCHAR(MAX)";
  97. }
  98. if (type == typeof(int)) return "INT";
  99. if (type == typeof(long)) return "BIGINT";
  100. if (type == typeof(short)) return "SMALLINT";
  101. if (type == typeof(byte)) return "TINYINT";
  102. if (type == typeof(bool)) return "BIT";
  103. if (type == typeof(decimal)) return "DECIMAL(38, 10)";
  104. if (type == typeof(double)) return "FLOAT";
  105. if (type == typeof(float)) return "REAL";
  106. if (type == typeof(DateTime)) return "DATETIME";
  107. if (type == typeof(Guid)) return "UNIQUEIDENTIFIER";
  108. if (type == typeof(byte[])) return "VARBINARY(MAX)";
  109. throw new NotSupportedException(string.Format("Unsupported DataColumn type: {0}, column: {1}", column.DataType.FullName, column.ColumnName));
  110. }
  111. private static void CheckArguments(SqlCommand cmd, string tempTableName, DataTable dataTable)
  112. {
  113. CheckCommand(cmd);
  114. CheckTempTableName(tempTableName);
  115. if (dataTable == null)
  116. {
  117. throw new ArgumentNullException("dataTable");
  118. }
  119. }
  120. private static void CheckCommand(SqlCommand cmd)
  121. {
  122. if (cmd == null)
  123. {
  124. throw new ArgumentNullException("cmd");
  125. }
  126. if (cmd.Connection == null)
  127. {
  128. throw new ArgumentException("SqlCommand must have a valid SqlConnection.", "cmd");
  129. }
  130. }
  131. private static void CheckTempTableName(string tempTableName)
  132. {
  133. if (string.IsNullOrWhiteSpace(tempTableName) || !TempTableNameRegex.IsMatch(tempTableName))
  134. {
  135. throw new ArgumentException("Temp table name must start with # or ## and contain only letters, numbers, and underscores.", "tempTableName");
  136. }
  137. }
  138. private static string QuoteName(string name)
  139. {
  140. return "[" + name.Replace("]", "]]") + "]";
  141. }
  142. }
  143. }