18using System.Collections.Generic;
20using System.Data.SqlClient;
24using System.Threading.Tasks;
29 internal class DatabaseHelper
31 public static SqlConnection OpenConnection(DatabaseSettings databaseSettings)
33 if (
null == databaseSettings.Connection)
37 if (
null != databaseSettings.ConnectionString)
38 databaseSettings.Connection =
new SqlConnection(databaseSettings.ConnectionString);
39 else if (Enums.DatabaseType.SQLServer == databaseSettings.DatabaseType)
41 StringBuilder sb =
new StringBuilder();
42 sb.Append($
"Data Source={databaseSettings.ServerName};Initial Catalog={databaseSettings.DatabaseName};");
43 if (
null == databaseSettings.UserName)
44 sb.Append(
"Integrated Security=true;");
47 sb.Append($
"User id={databaseSettings.UserName};password={databaseSettings.Password};");
49 databaseSettings.Connection =
new SqlConnection(sb.ToString());
52 databaseSettings.Connection.Open();
56 RecordFailure(databaseSettings, ex, $
"Unable to open a connection to [{databaseSettings.DatabaseName}] on [{databaseSettings.ServerName}]",
null,
null, -1);
59 return databaseSettings.Connection;
61 public static void Close(DatabaseSettings databaseSettings)
65 if (
null == databaseSettings.SqlTransaction)
67 databaseSettings.Connection.Close();
68 databaseSettings.Connection =
null;
78 public static void CloseEverything(DatabaseSettings databaseSettings)
80 if (
null != databaseSettings.SqlTransaction)
82 databaseSettings.SqlTransaction.Rollback();
83 databaseSettings.SqlTransaction =
null;
86 if (
null != databaseSettings.Connection)
88 if (ConnectionState.Closed != databaseSettings.Connection.State)
89 databaseSettings.Connection.Close();
91 databaseSettings.Connection.Dispose();
92 databaseSettings.Connection =
null;
102 private static void PrepareCommand(DatabaseSettings databaseSettings, SqlCommand command)
106 if (0 <= databaseSettings.CommandTimeoutSeconds)
107 command.CommandTimeout = databaseSettings.CommandTimeoutSeconds;
109 if (
null != databaseSettings.SqlTransaction)
110 command.Transaction = databaseSettings.SqlTransaction;
122 private static SqlCommand BuildReadCommand(DatabaseSettings databaseSettings, SqlConnection sqlConnection)
124 SqlCommand cmd =
null;
126 if (
null != databaseSettings.SqlCommand)
128 cmd =
new SqlCommand(databaseSettings.SqlCommand, sqlConnection)
130 CommandType = databaseSettings.SqlCommandType
133 else if (
null != databaseSettings.ReadSQL)
135 cmd =
new SqlCommand(databaseSettings.ReadSQL, sqlConnection)
137 CommandType = databaseSettings.SqlCommandType
140 else if (
null != databaseSettings.TableName)
142 string sqlCommand =
"Select " + ((-1 != databaseSettings.RowCount) ? $
"TOP({databaseSettings.RowCount}) " :
"") + $
"* from {databaseSettings.TableName}";
144 cmd =
new SqlCommand(sqlCommand, sqlConnection)
146 CommandType = CommandType.Text
149 else if (
null != databaseSettings.StoredProcedure)
152 cmd = sqlConnection.CreateCommand();
153 cmd.CommandType = CommandType.StoredProcedure;
154 cmd.CommandText = databaseSettings.StoredProcedure;
155 if (
null != databaseSettings.ParametersList)
157 foreach (
string parameter
in databaseSettings.ParameterNamesList)
158 cmd.Parameters.Add(
new SqlParameter(parameter, databaseSettings.ParametersList[parmIdx++]));
162 PrepareCommand(databaseSettings, cmd);
173 public static void GetScalar(DatabaseSettings databaseSettings, out
string value)
175 string retVal =
null;
176 SqlConnection sqlConnection = OpenConnection(databaseSettings);
177 SqlCommand cmd = BuildReadCommand(databaseSettings, sqlConnection);
181 object answer = cmd?.ExecuteScalar();
183 retVal = DBNull.Value == answer ? null : answer?.ToString();
185 Close(databaseSettings);
189 RecordFailure(databaseSettings, ex, $
"Failed reading with [{cmd?.CommandText}]", cmd?.CommandText, databaseSettings.ParametersGrid, -1);
201 public static int GetDataSet(DatabaseSettings databaseSettings, out DataSet dataSet)
203 DataSet returnDataSet =
new DataSet();
204 SqlConnection sqlConnection = OpenConnection(databaseSettings);
205 SqlCommand cmd = BuildReadCommand(databaseSettings, sqlConnection);
209 using var adapter =
new SqlDataAdapter(cmd);
210 adapter.Fill(returnDataSet);
211 Close(databaseSettings);
215 RecordFailure(databaseSettings, ex, $
"Failed reading with [{cmd?.CommandText}]", cmd?.CommandText, databaseSettings.ParametersGrid, -1);
218 dataSet = returnDataSet;
219 return dataSet.Tables.Count;
225 public static int GetGrid(DatabaseSettings databaseSettings, out IGPALGrid<string> dataGrid)
227 IGPALGrid<string> returnDataGrid = GPAL.Grid.ToGPALObject();
228 GetDataSet(databaseSettings, out DataSet dataSet);
230 var rowCountsPerTable =
new List<int>();
232 foreach (DataTable table
in dataSet.Tables)
234 if (databaseSettings.ColumnList.Count == 0)
236 foreach (DataColumn column
in table.Columns)
238 SqlDbType sqlDbType = GetSqlDbTypeFromClrType(column.DataType);
239 databaseSettings.ColumnList.Add(
new DatabaseSettings.DatabaseColumn(column.ColumnName, sqlDbType));
243 int rowsAddedForTable = 0;
244 foreach (DataRow row
in table.Rows)
246 if (0 < databaseSettings.RowCount && rowsAdded >= databaseSettings.RowCount)
248 returnDataGrid.AddRow(row.ItemArray.Select(v => v.ToString()).ToList());
252 rowCountsPerTable.Add(rowsAddedForTable);
254 databaseSettings.LastGridRowCountsPerTable = rowCountsPerTable;
256 dataGrid = returnDataGrid;
257 return dataGrid.Count();
263 public static int GetDataTable(DatabaseSettings databaseSettings, out DataTable dataTable)
265 GetDataSet(databaseSettings, out DataSet dataSet);
267 if (dataSet.Tables.Count > 1)
269 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
270 $
"Query returned [{dataSet.Tables.Count}] result sets; SaveTo(out DataTable) only keeps the first. Use SaveTo(out DataSet) to get all of them.",
271 databaseSettings.Database, GPALObjectType.Database);
274 dataTable = dataSet.Tables.Count > 0 ? dataSet.Tables[0] :
new DataTable();
275 return dataTable.Rows.Count;
277 public static int Insert(DatabaseSettings databaseSettings, IGPALGrid<string> tokens)
279 StringBuilder insertCommand =
new StringBuilder();
280 StringBuilder insertParams =
new StringBuilder();
282 insertCommand.Append($
"INSERT INTO {databaseSettings.TableName} (");
284 foreach (DatabaseSettings.DatabaseColumn databaseColumn in databaseSettings.ColumnList)
286 insertCommand.Append($
"{databaseColumn.ColumName}, ");
287 insertParams.Append($
"@{databaseColumn.ColumName}, ");
289 insertCommand.Remove(insertCommand.Length - 1, 1);
290 insertCommand.Append(
") Values (");
291 insertParams.Remove(insertParams.Length - 1, 1);
292 insertParams.Append(
")");
293 insertCommand.Append(insertParams);
295 return ExecuteSQL(databaseSettings, insertCommand.ToString(), tokens);
305 internal static int ExecuteSQL(DatabaseSettings databaseSettings,
string sqlToRun, IGPALGrid<string> parameterTokens =
null)
307 return ExecuteSQL(databaseSettings, sqlToRun, parameterTokens, out _);
311 internal static int ExecuteSQL(DatabaseSettings databaseSettings,
string sqlToRun, IGPALGrid<string> parameterTokens, out List<int> rowCountsPerStatement)
313 using var sqlCommand =
new SqlCommand(sqlToRun, databaseSettings.Connection ?? OpenConnection(databaseSettings))
315 CommandType = databaseSettings.SqlCommandType
318 PrepareCommand(databaseSettings, sqlCommand);
321 int rowBeingWritten = -1;
322 bool ranPerRow =
false;
323 var statementCounts =
new List<int>();
326 databaseSettings.LastError =
null;
327 sqlCommand.StatementCompleted += (s, e) => statementCounts.Add(e.RecordCount);
331 if (parameterTokens !=
null && parameterTokens.Rows > 0)
333 var structuredColumn = databaseSettings.ColumnList.FirstOrDefault(c => c.ColumnType == SqlDbType.Structured);
334 if (structuredColumn !=
null)
336 if (
string.IsNullOrEmpty(structuredColumn.TvTableTypeName))
337 throw new InvalidOperationException($
"UDTT Type Name required for Table Value column @[{structuredColumn.ColumName}]. Use .WithUDDTName()");
339 var dataTable =
new DataTable();
340 int columnCount = parameterTokens.Columns;
342 for (
int i = 0; i < columnCount; i++)
344 dataTable.Columns.Add($
"Column{i}", typeof(
string));
347 foreach (var row
in parameterTokens)
349 if (row.Count != columnCount)
351 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
352 $
"Row [{row}] has [{row.Count}] values, but expected [{columnCount}]. Skipping row.",
353 parameterTokens, GPALObjectType.Other);
356 dataTable.Rows.Add(row.ToArray());
359 var param = sqlCommand.Parameters.AddWithValue($
"@{structuredColumn.ColumName}", dataTable);
360 param.SqlDbType = SqlDbType.Structured;
361 param.TypeName = structuredColumn.TvTableTypeName;
369 foreach (var parameters
in parameterTokens)
372 sqlCommand.Parameters.Clear();
374 foreach (var databaseColumn
in databaseSettings.ColumnList)
376 int columnIndex = databaseSettings.ColumnList.IndexOf(databaseColumn);
377 string paramValue = parameters.Count > columnIndex ? parameters[columnIndex] :
null;
378 var convertedValue = ReturnParameterOfType(paramValue, databaseColumn.ColumnType);
379 sqlCommand.Parameters.Add($
"@{databaseColumn.ColumName}", databaseColumn.ColumnType, databaseColumn.ColumnLength).Value = convertedValue;
382 rowCount += sqlCommand.ExecuteNonQuery();
391 if (
false == ranPerRow)
392 rowCount = sqlCommand.ExecuteNonQuery();
397 RecordFailure(databaseSettings, ex, $
"Failed running [{sqlToRun}]", sqlToRun, parameterTokens, rowBeingWritten);
400 rowCountsPerStatement = statementCounts;
413 private static void RecordFailure(DatabaseSettings databaseSettings, Exception ex,
string message,
string sql, IGPALGrid<string> parameterTokens,
int row)
415 databaseSettings.LastError =
new GPALDatabase.DatabaseError
417 Number = ex is SqlException sqlException ? sqlException.Number : 0,
418 Message = ex.Message,
420 Parameters = parameterTokens,
422 RowValues = 0 <= row && row < (parameterTokens?.Rows ?? 0) ?
new List<string>(parameterTokens[row]) : null,
423 Occurred = DateTime.Now,
432 GPAL.PublishSimpleEvent(ex is SqlException ? GPALEventType.ERROR : GPALEventType.EXCEPTION,
433 message, databaseSettings.Database, GPALObjectType.Database, ex);
435 CallOnErrorHandler(databaseSettings);
438 private static void CallOnErrorHandler(DatabaseSettings databaseSettings)
440 if (
null != databaseSettings.CallOnErrorHandler)
441 databaseSettings.CallOnErrorHandler(databaseSettings.Database);
443 if (
null != databaseSettings.SqlTransaction)
445 databaseSettings.SqlTransaction.Rollback();
446 databaseSettings.SqlTransaction =
null;
449 if (
null != databaseSettings.Connection && ConnectionState.Open == databaseSettings.Connection.State)
451 databaseSettings.Connection.Close();
452 databaseSettings.Connection =
null;
455 static SqlDbType GetSqlDbTypeFromName(
string dataTypeName)
457 switch (dataTypeName.ToLower())
460 return SqlDbType.Char;
462 return SqlDbType.NChar;
464 return SqlDbType.NText;
466 return SqlDbType.NVarChar;
468 return SqlDbType.Text;
470 return SqlDbType.VarChar;
472 return SqlDbType.Xml;
474 return SqlDbType.Date;
476 return SqlDbType.DateTime;
478 return SqlDbType.DateTime2;
479 case "smalldatetime":
480 return SqlDbType.SmallDateTime;
482 return SqlDbType.Time;
484 return SqlDbType.Timestamp;
486 return SqlDbType.Decimal;
488 return SqlDbType.Money;
490 return SqlDbType.SmallMoney;
492 return SqlDbType.Float;
494 return SqlDbType.Real;
496 return SqlDbType.Image;
498 return SqlDbType.Bit;
500 return SqlDbType.Int;
502 return SqlDbType.SmallInt;
505 throw new ArgumentException(
"Unknown SqlDbType: " + dataTypeName);
512 static SqlDbType GetSqlDbTypeFromClrType(Type clrType)
514 switch (Type.GetTypeCode(clrType))
516 case TypeCode.String:
return SqlDbType.NVarChar;
517 case TypeCode.Boolean:
return SqlDbType.Bit;
518 case TypeCode.Byte:
return SqlDbType.TinyInt;
519 case TypeCode.Int16:
return SqlDbType.SmallInt;
520 case TypeCode.Int32:
return SqlDbType.Int;
521 case TypeCode.Int64:
return SqlDbType.BigInt;
522 case TypeCode.Single:
return SqlDbType.Real;
523 case TypeCode.Double:
return SqlDbType.Float;
524 case TypeCode.Decimal:
return SqlDbType.Decimal;
525 case TypeCode.DateTime:
return SqlDbType.DateTime;
527 if (clrType == typeof(Guid))
return SqlDbType.UniqueIdentifier;
528 if (clrType == typeof(
byte[]))
return SqlDbType.VarBinary;
529 if (clrType == typeof(TimeSpan))
return SqlDbType.Time;
530 if (clrType == typeof(DateTimeOffset))
return SqlDbType.DateTimeOffset;
531 return SqlDbType.NVarChar;
534 private static object ReturnParameterOfType(
string parmValue, SqlDbType sqlDbType)
536 if (
string.IsNullOrEmpty(parmValue))
538 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
539 $
"Parameter value is null or empty for SqlDbType [{sqlDbType}]. Returning DBNull.Value.",
540 null, GPALObjectType.Other);
549 case SqlDbType.NChar:
550 case SqlDbType.NText:
551 case SqlDbType.NVarChar:
553 case SqlDbType.VarChar:
558 case SqlDbType.DateTime:
559 case SqlDbType.DateTime2:
560 case SqlDbType.SmallDateTime:
562 return DateTime.TryParse(parmValue, out DateTime dateResult) ? (object)dateResult : DBNull.Value;
564 case SqlDbType.DateTimeOffset:
565 return DateTimeOffset.TryParse(parmValue, out DateTimeOffset offsetResult) ? (object)offsetResult : DBNull.Value;
567 case SqlDbType.Decimal:
568 case SqlDbType.Money:
569 case SqlDbType.SmallMoney:
570 return Decimal.TryParse(parmValue, out decimal decimalResult) ? (object)decimalResult : DBNull.Value;
572 case SqlDbType.Float:
574 return float.TryParse(parmValue, out
float floatResult) ? (object)floatResult : DBNull.Value;
577 return bool.TryParse(parmValue, out
bool boolResult) ? (object)boolResult : (int.TryParse(parmValue, out int intBitResult) ? (object)(intBitResult != 0) : DBNull.Value);
580 case SqlDbType.SmallInt:
581 return int.TryParse(parmValue, out
int intResult) ? (
object)intResult : DBNull.Value;
583 case SqlDbType.BigInt:
584 return long.TryParse(parmValue, out
long longResult) ? (
object)longResult : DBNull.Value;
586 case SqlDbType.UniqueIdentifier:
587 return Guid.TryParse(parmValue, out Guid guidResult) ? (
object)guidResult : DBNull.Value;
589 case SqlDbType.Image:
590 case SqlDbType.VarBinary:
591 case SqlDbType.Binary:
594 return Convert.FromBase64String(parmValue);
596 catch (FormatException ex)
598 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
599 $
"Failed to decode Base64 string [{parmValue}] for SqlDbType [{sqlDbType}]. Returning DBNull.Value.",
600 null, GPALObjectType.Other, ex);
604 case SqlDbType.Timestamp:
607 if (ulong.TryParse(parmValue, out ulong ulongResult))
609 return BitConverter.GetBytes(ulongResult).Reverse().ToArray();
611 throw new FormatException(
"Invalid numeric string for Timestamp");
615 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
616 $
"Failed to parse [{parmValue}] as Timestamp. Returning DBNull.Value.",
617 null, GPALObjectType.Other, ex);
621 case SqlDbType.Variant:
622 if (
int.TryParse(parmValue, out
int variantIntResult))
623 return variantIntResult;
624 if (decimal.TryParse(parmValue, out decimal variantDecResult))
625 return variantDecResult;
626 if (DateTime.TryParse(parmValue, out DateTime variantDateResult))
627 return variantDateResult;
628 if (Guid.TryParse(parmValue, out Guid variantGuidResult))
629 return variantGuidResult;
632 case SqlDbType.Structured:
633 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
634 $
"SqlDbType.Structured is not supported for individual parameter values. Use a multi-row GPALGrid<string> in ExecuteSQL. Returning DBNull.Value.",
635 null, GPALObjectType.Other);
639 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
640 $
"Unsupported SqlDbType [{sqlDbType}] for parameter value [{parmValue}]. Returning DBNull.Value.",
641 null, GPALObjectType.Other);
647 GPAL.PublishSimpleEvent(GPALEventType.WARNING,
648 $
"Failed to convert parameter value [{parmValue}] to SqlDbType [{sqlDbType}]. Returning DBNull.Value.",
649 null, GPALObjectType.Other, ex);
657 internal static void TokenizeDatabase(UnitOfWork currentUOW, IGPALDatabase inputDatabase)
659 IGPALGrid<string> tokens;
660 if (
null != currentUOW)
661 currentUOW.InputDatabase = inputDatabase;
662 int rowCount = DatabaseHelper.GetGrid(((GPALDatabase)inputDatabase).DatabaseSettings, out tokens);
663 ((GPALDatabase)inputDatabase).Tokens = tokens;
672 public static IGPALGrid<string> DataTableToGrid(DataTable dataTable, out List<DatabaseSettings.DatabaseColumn> columnsList)
674 IGPALGrid<string> retVal = GPAL.Grid.ToGPALObject();
676 columnsList =
new List<DatabaseSettings.DatabaseColumn>();
678 if (
null != dataTable)
680 foreach (DataColumn column
in dataTable.Columns)
681 columnsList.Add(
new DatabaseSettings.DatabaseColumn(column.ColumnName, GetSqlDbTypeFromClrType(column.DataType)));
683 foreach (DataRow row
in dataTable.Rows)
684 retVal.AddRow(row.ItemArray.Select(value => DBNull.Value == value ?
null : value?.ToString()).ToList());
689 public static void TokenizeDatabase(GPALDatabase inputDatabase)
691 IGPALGrid<string> tokens;
692 int rowCount = DatabaseHelper.GetGrid(inputDatabase.DatabaseSettings, out tokens);
693 inputDatabase.Tokens = tokens;
701 public static class DataTableExtensions
703 public static void SetColumnsOrder(
this DataTable table, params String[] columnNames)
706 foreach (var columnName
in columnNames)
708 if (table.Columns.Contains(columnName))
710 table.Columns[columnName].SetOrdinal(columnIndex);