using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Runtime.CompilerServices; using System.IO; using System.Xml; using System.Security.Principal; using System.Net; using System.Globalization; using C4IT.Logging; using C4IT.Security; using C4IT.XML; using C4IT.FASD.Base; using static C4IT.Logging.cLogManager; #if !NETSTANDARD && !NETCORE using System.Data.SqlClient; #else using Microsoft.Data.SqlClient; #endif namespace C4IT.DataHistoryProvider { public static class DataHistorySqlHelper { public const string constWellDefinedSqlFile = "DataHistorySql"; public const int constDefaultSqlTimeout = 20; public const string constBinValueNameAdd = "_bin"; private static string ApplicationName = null; private static string WorkstationID = null; public readonly static Dictionary wellDefinedSqlStatements = new Dictionary(); public static int lastSqlConnectionError { get; private set; } = 0; public static bool LogSql = false; public static string SqlLogFileName = null; public static object SqlLogSync = new object(); public static void Initialize(Assembly Ass = null) { if (WorkstationID != null) return; MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { if (Ass == null) Ass = Assembly.GetExecutingAssembly(); ApplicationName = cLogManagerFile.GetAssemblyTitle(Ass); WorkstationID = Environment.MachineName; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } } static DataHistorySqlHelper() { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { Initialize(); // initialize encryption enclave providers at statup, because this will not work if the first sql operation is impersonated // see https://support.microsoft.com/en-us/topic/sqlconnection-instantiation-exception-on-net-4-6-and-later-after-august-september-2018-net-framework-updates-8486716b-6f39-8ed5-37f6-25487ee8b9ab // see https://stackoverflow.com/questions/12791207/catastrophic-failure-when-impersonate-other-user/16777117#16777117 System.Configuration.ConfigurationManager.GetSection("SqlColumnEncryptionEnclaveProviders"); System.Configuration.ConfigurationManager.GetSection("SectionNameThatDoesNotExistInAnyConfigurationFile"); } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } } public static void SaveSqlTimingEntry(string SqlQueryName, DateTime start, double duration, string SqlQueryParams = null, int error = 0, string OriginName = null) { try { if (!LogSql || SqlLogFileName == null) return; var _strStart = start.ToString("yyyy-MM-dd-HH-mm-ss-FFFFF"); var _strEnd = DateTime.UtcNow.ToString("yyyy-MM-dd-HH-mm-ss-FFFFF"); var _line = string.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\r\n", _strStart, _strEnd, SqlQueryName, duration, error, SqlQueryParams ?? "", OriginName ?? ""); lock (SqlLogSync) System.IO.File.AppendAllText(SqlLogFileName, _line, Encoding.UTF8); } catch (Exception E) { LogException(E); } } public static void SaveSqlTimingEntry(string SqlQueryName, DateTime timeStart, string SqlQueryParams = null, int error = 0, string OriginName = null) { try { if (!LogSql || SqlLogFileName == null) return; var end = DateTime.UtcNow; var duration = (end - timeStart).TotalMilliseconds; SaveSqlTimingEntry(SqlQueryName, timeStart, duration, SqlQueryParams, error, OriginName); } catch (Exception E) { LogException(E); } } public static void SaveSqlTimingEntry(string SqlQueryName, DateTime start, double duration, Dictionary SqlQueryParams, int error = 0, string OriginName = null) { try { if (!LogSql || SqlLogFileName == null) return; var strSqlParams = ""; if (SqlQueryParams != null) { foreach (var _entry in SqlQueryParams) { if (!string.IsNullOrEmpty(strSqlParams)) strSqlParams += "|"; strSqlParams += _entry.Key + "=" + _entry.Value?.ToString() ?? ""; } } SaveSqlTimingEntry(SqlQueryName, start, duration, strSqlParams, error, OriginName); } catch (Exception E) { LogException(E); } } public static void SaveSqlTimingEntry(string SqlQueryName, DateTime timeStart, Dictionary SqlQueryParams, int error = 0, string OriginName = null) { try { if (!LogSql || SqlLogFileName == null) return; var end = DateTime.UtcNow; var duration = (end - timeStart).TotalMilliseconds; SaveSqlTimingEntry(SqlQueryName, timeStart, duration, SqlQueryParams, error, OriginName); } catch (Exception E) { LogException(E); } } public static bool LoadWellKnownSqlStatements(out List ParsingMessages, Assembly ExecutingAssembly = null) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } ParsingMessages = new List(); cXmlParser Parser = null; try { wellDefinedSqlStatements.Clear(); var Locations = new List() { new cXmlFileLocation() { LocationType = cXmlFileLocation.enumXmlLocationType.ProgramFolder, SubFolder = "", Location = constWellDefinedSqlFile + ".xml", ExecutingAssembly = ExecutingAssembly }, new cXmlFileLocation() { LocationType = cXmlFileLocation.enumXmlLocationType.EmbeddedResource, SubFolder = "", Location = constWellDefinedSqlFile + ".xml", ExecutingAssembly = ExecutingAssembly } }; var XmlRoot = cXmlParser.OpenXmlDocument(Locations, out Parser, constWellDefinedSqlFile); if (XmlRoot == null || Parser == null || !Parser.IsValid) return false; var XQueries = XmlRoot.SelectSingleNode("Queries"); if (XQueries != null) { Parser.EnterElement("Queries"); var XList = XQueries.SelectNodes("Query"); if (XList != null && XList.Count > 0) { Parser.EnterElement("Query"); foreach (var Entry in XList) { if (!(Entry is XmlElement XQuery)) continue; var Q = new cDbQuery { Name = cXmlParser.GetStringFromXmlAttribute(XQuery, "Name"), Query = cXmlParser.GetInnerTextFromXmlElement(XQuery), TimeoutFactor = cXmlParser.GetIntegerFromXmlAttribute(XQuery, "TimeoutFactor", 1), Subsequent = cXmlParser.GetIntegerFromXmlAttribute(XQuery, "Subsequent", 0), Description = cXmlParser.GetStringFromXmlAttribute(XQuery, "Description", "") }; if (string.IsNullOrWhiteSpace(Q.Name)) { Parser.AddInvalidName(XQuery); continue; } else if (string.IsNullOrWhiteSpace(Q.Query)) { Parser.AddMessage(XQuery, $"No SQL statement in element "); continue; } else if (wellDefinedSqlStatements.ContainsKey(Q.Name)) { Parser.AddDuplicateName(XQuery, Q.Name); continue; } wellDefinedSqlStatements.Add(Q.Name, Q); Parser.SelectElementNext(); } Parser.LeaveElement("Query"); } Parser.LeaveElement("Queries"); } if (Parser.LogMessages.Count == 0) return true; } catch (Exception E) { LogException(E); } finally { if (Parser != null) ParsingMessages = Parser.LogMessages; if (CM != null) LogMethodEnd(CM); } return false; } public static bool GetWellKnownSqlStatement(string Name, out cDbQuery Query) { if (wellDefinedSqlStatements.TryGetValue(Name, out var RetVal)) { Query = RetVal; return true; } Query = null; return false; } private static void GetFormatters(bool formatted, out string strCF, out string strTab) { strCF = " "; strTab = ""; if (formatted) { strCF = "\n"; strTab = "\t"; } } public static string GetSqlCreateStatement(cDataHistoryConfigTable Table, bool UseBinValuesAsPrimaryKey, bool formatted, bool Full = false) { GetFormatters(formatted, out var strCF, out var strTab); MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var strColumns = "[ScanId] [uniqueidentifier] NOT NULL"; if (Table.Type == eDataHistoryTableType.Events) strColumns += ", [EventId] [uniqueidentifier] NOT NULL"; foreach (var Entry in Table.Columns.Values) { if (Entry is cDataHistoryConfigColumnComputation) continue; if (strColumns != "") strColumns += "," + strCF; strColumns += strTab; var strNull = "NULL"; if (!Table.IsNullable(Entry)) strNull = "NOT " + strNull; strColumns += string.Format("[{0}] {1} {2}", Entry.Name, Entry.SqlType, strNull); if (Entry.SqlTypeBin != null) { strColumns += "," + strCF + strTab + string.Format("[{0}{1}] {2} {3}", Entry.Name, constBinValueNameAdd, Entry.SqlTypeBin, strNull); } } var strSql = string.Format("CREATE TABLE [{0}] (", Table.SourceName) + strCF + strColumns + strCF + GetSqlPrimaryKeyStatement(Table, UseBinValuesAsPrimaryKey, formatted) + ");"; strSql += strCF + strCF + $"ALTER TABLE [{Table.SourceName}] ADD CONSTRAINT [DF_{Table.SourceName}_ScanId] DEFAULT ('00000000-0000-0000-0000-000000000000') FOR [ScanId];"; if (Table.Type == eDataHistoryTableType.Events) strSql += strCF + strCF + $"ALTER TABLE [{Table.SourceName}] ADD CONSTRAINT [DF_{Table.SourceName}_eventId] DEFAULT (newid()) FOR [EventId];"; foreach (var Index in Table.Indexes.Values) strSql += strCF + strCF + GetSqlIndexStatement(Index, Table, formatted); if (Full) { if (Table.Type == eDataHistoryTableType.Events || Table.Type == eDataHistoryTableType.History || Table.Type == eDataHistoryTableType.HistoryEvents) strSql += strCF + $"ALTER TABLE [{Table.SourceName}] WITH CHECK ADD CONSTRAINT [FK_{Table.SourceName}_scan-history] FOREIGN KEY([ScanId]) REFERENCES [main-scan-history] ([ScanId]) ON DELETE CASCADE;"; foreach (var Ref in Table.References.Values) strSql += strCF + GetReferencingStatements(Table, Ref, formatted); } return strSql; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } private static string GetSqlPrimaryKeyStatement(cDataHistoryConfigTable Table, bool UseBinValuesAsPrimaryKey, bool formatted) { try { GetFormatters(formatted, out var strCF, out var strTab); var strSql = ""; foreach (var Col in Table.KeyColumns) { if (strSql != "") strSql += "," + strCF; strSql += strTab; var strColName = Col.Name; if (UseBinValuesAsPrimaryKey && Col.SqlTypeBin != null) strColName += constBinValueNameAdd; strSql += string.Format("[{0}] ASC", strColName); } if (Table.Type == eDataHistoryTableType.History) strSql += "," + strCF + "[ScanId]"; else if (Table.Type == eDataHistoryTableType.Events) { strSql += "," + strCF + $"[{Table.EventTimeColumn.Name}]"; strSql += "," + strCF + "[EventId]"; } else if (Table.Type == eDataHistoryTableType.HistoryEvents) { strSql += "," + strCF + "[ScanId]"; strSql += "," + strCF + $"[{Table.EventTimeColumn.Name}]"; } strSql = $"CONSTRAINT [PK_{Table.SourceName}]" + strCF + "PRIMARY KEY CLUSTERED (" + strCF + strSql + ")"; return strSql; } catch (Exception E) { LogException(E); } return ""; } private static string GetSqlIndexStatement(cDataHistoryConfigTableIndex Index, cDataHistoryConfigTable Table, bool formatted = false) { try { GetFormatters(formatted, out var strCF, out var strTab); var strCols = ""; foreach (var Entry in Index.Columns) { if (strCols != "") strCols += "," + strCF; strCols += strTab + "[" + Entry.Name + "] ASC"; } var strUnique = ""; if (Index.Unique) strUnique = " UNIQUE"; var strSql = $"CREATE{strUnique} INDEX [{Index.Name}]{strCF}ON [{Table.SourceName}] ({strCF}{strCols})"; return strSql; } catch (Exception E) { LogException(E); } return ""; } public static string GetReferencingName(cDataHistoryConfigTable Table, cDataHistoryConfigTableReferenceInfo RefInfo) { return string.Format("FK_{0}_{1}", Table.SourceName, RefInfo.ForeignTable.SourceName); } public static string GetReferencingStatements(cDataHistoryConfigTable Table, cDataHistoryConfigTableReferenceInfo Ref, bool formatted = false) { try { GetFormatters(formatted, out var strCF, out var strTab); var Result = ""; Result += strCF; var strTable = Table.SourceName; var strTableRef = Ref.ForeignTableName; var strName = GetReferencingName(Table, Ref); var strCols = ""; var strColsRef = ""; var ColsForeign = Ref.ReferencedColumns.GetEnumerator(); foreach (var Col in Ref.Columns) { if (!ColsForeign.MoveNext()) return ""; var ColRef = ColsForeign.Current; var strBin = ""; if (Col.SqlTypeBin != null && ColRef.SqlTypeBin != null) strBin = "_bin"; if (strCols != "") strCols += ", "; strCols += strCF + strTab + "[" + Col.Name + strBin + "]"; if (strColsRef != "") strColsRef += ", "; strColsRef += strCF + strTab + "[" + ColRef.Name + strBin + "]"; } var Line = string.Format("ALTER TABLE [{0}]{5}WITH CHECK ADD CONSTRAINT [{1}]{5}FOREIGN KEY ({2}){5}REFERENCES [{3}] ({4})", strTable, strName, strColsRef, strTableRef, strCols, strCF); Result += Line; return Result; } catch (Exception E) { LogException(E); } return ""; } internal static object[] _GetSqlValuesVersion(object val) { try { if (val == null) return new object[2] { null, null }; Version v = null; if (val is Version _ver) v = _ver; else if (val is byte[] _arr) { if (_arr.Length <= 8 && ((_arr.Length & 1) == 0)) { var strVersion = ""; for (int i = 0; i < _arr.Length; i += 2) { if (strVersion != "") strVersion += '.'; strVersion += ((int)_arr[i] + ((int)_arr[i + 1]) >> 8).ToString(); } return new object[2] { strVersion, _arr }; } } else if (Version.TryParse(val.ToString(), out var _ver2)) v = _ver2; if (v != null) { byte[] _arr = null; if (v.Major < 0) return null; else if (v.Minor < 0) _arr = new byte[2] { (byte)(v.Major & 0xFF), (byte)((v.Major & 0xFF00) << 8) }; else if (v.Build < 0) _arr = new byte[4] { (byte)(v.Minor & 0xFF), (byte)((v.Minor & 0xFF00) << 8), (byte)(v.Major & 0xFF), (byte)((v.Major & 0xFF00) << 8) }; else if (v.Revision < 0) _arr = new byte[6] { (byte)(v.Build & 0xFF), (byte)((v.Build & 0xFF00) << 8), (byte)(v.Minor & 0xFF), (byte)((v.Minor & 0xFF00) << 8), (byte)(v.Major & 0xFF), (byte)((v.Major & 0xFF00) << 8) }; else _arr = new byte[8] { (byte)(v.Revision & 0xFF), (byte)((v.Revision & 0xFF00) << 8), (byte)(v.Build & 0xFF), (byte)((v.Build & 0xFF00) << 8), (byte)(v.Minor & 0xFF), (byte)((v.Minor & 0xFF00) << 8), (byte)(v.Major & 0xFF), (byte)((v.Major & 0xFF00) << 8) }; return new object[2] { v.ToString(), _arr }; } } catch (Exception E) { LogException(E); } return new object[2] { null, null }; ; } internal static object[] _GetSqlValuesMD5(object val) { try { if (val == null) return new object[2] { null, null }; if (val is byte[] _arr) { if (_arr.Length == 16) { StringBuilder result = new StringBuilder(_arr.Length * 2); for (int i = 0; i < _arr.Length; i++) result.Append(_arr[i].ToString("X2")); return new object[2] { result, _arr }; } return null; } var strMD5 = val.ToString(); var _arr2 = new byte[16]; if (strMD5.Length == 32) { for (int i = 0; i < 16; i++) { var strHex = strMD5.Substring(i * 2, 2); _arr2[i] = Convert.ToByte(strHex, 16); } return new object[2] { strMD5, _arr2 }; } } catch (Exception E) { LogException(E); } return new object[2] { null, null }; ; } internal static object[] _GetSqlValuesSID(object val) { try { if (val == null) return new object[2] { null, null }; if (val is byte[] arrByte) { var sid = new SecurityIdentifier(arrByte, 0); var strSid = sid.ToString(); return new object[2] { strSid, arrByte }; } var sid2 = new SecurityIdentifier(val.ToString()); var _arr = new byte[sid2.BinaryLength]; sid2.GetBinaryForm(_arr, 0); return new object[2] { sid2.ToString(), _arr }; } catch (Exception E) { LogException(E); } return new object[2] { null, null }; ; } internal static object[] _GetSqlValuesIP4(object val) { try { if (val == null) return new object[2] { null, null }; if (val is byte[] arrByte && arrByte.Length == 4) { var _ip = new IPAddress(arrByte); var strIP = _ip.ToString(); return new object[2] { strIP, arrByte }; } var strIp = val.ToString(); if (IPAddress.TryParse(strIp, out var _ip2)) { if (_ip2.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { var _arr = _ip2.GetAddressBytes(); return new object[2] { _ip2.ToString(), _arr }; } } } catch (Exception E) { LogException(E); } return new object[2] { null, null }; ; } internal static object[] _GetSqlValuesIP6(object val) { try { if (val == null) return new object[2] { null, null }; if (val is byte[] arrByte) { var _ip = new IPAddress(arrByte); if (_ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetworkV6) { var strIP = _ip.ToString(); return new object[2] { strIP, arrByte }; } } var strIp = val.ToString(); if (IPAddress.TryParse(strIp, out var _ip2)) { if (_ip2.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) { var _arr = _ip2.GetAddressBytes(); return new object[2] { _ip2.ToString(), _arr }; } } } catch (Exception E) { LogException(E); } return new object[2] { null, null }; } internal static object[] _GetSqlValuesString(object val, int Cardinal) { try { var s1 = val.ToString(); if (Cardinal >= 0 && s1.Length > Cardinal) s1 = s1.Substring(0, Cardinal); return new object[1] { s1 }; } catch (Exception E) { LogException(E); } return new object[1] { null }; } internal static object[] _GetSqlValuesGuid(object val) { try { if (val is Guid _guid) return new object[1] { _guid }; if (Guid.TryParse(val.ToString(), out var _guid2)) return new object[1] { _guid2 }; } catch (Exception E) { LogException(E); } return new object[1] { null }; } internal static object[] _GetSqlValuesInt(object val) { try { if (val is Int32 _int) return new object[1] { _int }; var _int2 = Convert.ToInt32(val); return new object[1] { _int2 }; } catch (Exception E) { LogException(E); } return new object[1] { null }; } internal static object[] _GetSqlValuesBigInt(object val) { try { if (val is Int64 _int) return new object[1] { _int }; var _int2 = Convert.ToInt64(val); return new object[1] { _int2 }; } catch (Exception E) { LogException(E); } return new object[1] { null }; } internal static object[] _GetSqlValuesFloat(object val) { try { if (val is float _f) return new object[1] { _f }; var _f2 = Convert.ToSingle(val); return new object[1] { _f2 }; } catch (Exception E) { LogException(E); } return new object[1] { null }; } internal static object[] _GetSqlValuesDateTime(object val) { try { if (val is DateTime _dt) { if (_dt.Kind != DateTimeKind.Utc) _dt = DateTime.SpecifyKind(_dt, DateTimeKind.Utc); return new object[1] { _dt }; } if (DateTime.TryParse(val.ToString(), CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.AdjustToUniversal, out var _dt2)) { if (_dt2.Kind != DateTimeKind.Utc) _dt2 = DateTime.SpecifyKind(_dt2, DateTimeKind.Utc); return new object[1] { _dt2 }; } } catch (Exception E) { LogException(E); } return new object[1] { null }; } public static object[] GetSqlDataValueFromHistoryType(object val, enumFasdValueType type, int Cardinal) { try { object[] retVal = null; switch (type) { case enumFasdValueType.VERSION: retVal = _GetSqlValuesVersion(val); break; case enumFasdValueType.MD5: retVal = _GetSqlValuesMD5(val); break; case enumFasdValueType.SID: retVal = _GetSqlValuesSID(val); break; case enumFasdValueType.IPV4: retVal = _GetSqlValuesIP4(val); break; case enumFasdValueType.IPV6: retVal = _GetSqlValuesIP6(val); break; } if (retVal != null) { if (retVal.Length > 1 && retVal[1] == null) retVal[1] = System.Data.SqlTypes.SqlBinary.Null; return retVal; } if (val == null) return new object[1] { null }; if (type != enumFasdValueType.STRING && val is string str) if (str == "") return null; switch (type) { case enumFasdValueType.STRING: return _GetSqlValuesString(val, Cardinal); case enumFasdValueType.GUID: return _GetSqlValuesGuid(val); case enumFasdValueType.INT: return _GetSqlValuesInt(val); case enumFasdValueType.BIGINT: return _GetSqlValuesBigInt(val); case enumFasdValueType.FLOAT: return _GetSqlValuesFloat(val); case enumFasdValueType.DATETIME: return _GetSqlValuesDateTime(val); } } catch (Exception E) { LogException(E); } return null; } public static string CreateConnectionString(cDataHistoryConfigSqlConnection Connection, int Timeout = -1, bool MARS = false, bool WithoutDatabase = false) { try { var _connBuilder = new SqlConnectionStringBuilder() { ApplicationName = ApplicationName, WorkstationID = WorkstationID, MultipleActiveResultSets = MARS, IntegratedSecurity = !Connection.NativeAccount }; if (Timeout >= 0) _connBuilder.ConnectTimeout = Timeout; else if (Connection.Timeout >= 0) _connBuilder.ConnectTimeout = Connection.Timeout; else _connBuilder.ConnectTimeout = constDefaultSqlTimeout; var _DataSource = Connection.Server; if (!string.IsNullOrWhiteSpace(Connection.Instance)) _DataSource += @"\" + Connection.Instance; _connBuilder.DataSource = _DataSource; if (!WithoutDatabase) _connBuilder.InitialCatalog = Connection.Database; var RetVal = _connBuilder.ConnectionString; return RetVal; } catch (Exception E) { LogException(E); } return null; } public static SqlConnection OpenConnection(cDataHistoryConfigSqlConnection Connection, int Timeout = -1, bool MARS = false, bool WithoutDatabase = false, bool LogError = true) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { lastSqlConnectionError = 0; SqlConnection _conn = null; var _cs = CreateConnectionString(Connection, Timeout, MARS, WithoutDatabase); try { if (Connection.NativeAccount) { Connection.Credential.Password.MakeReadOnly(); _conn = new SqlConnection(_cs, new SqlCredential(Connection.Credential.User, Connection.Credential.Password)); _conn.Open(); } else { WindowsIdentity.RunImpersonated(Connection.Credential.SafeAccessToken, () => { try { if (System.Diagnostics.Debugger.IsAttached) // when running locally, the sql server does not have a certificate. _cs += ";TrustServerCertificate=True"; _conn = new SqlConnection(_cs); _conn.Open(); } catch (Exception ex) { LogException(ex); } }); } } #if NETFRAMEWORK catch (SqlException E) { lastSqlConnectionError = E.Number; if (LogError) LogEntry(string.Format("error opening sql connection '{0}': {1}, {2}", _cs, E.Number, E.Message), LogLevels.Error); throw; } #endif catch { if (LogError) LogEntry(string.Format("error opening sql connection: {0}", _cs), LogLevels.Error); throw; } if (_conn == null || _conn.State != System.Data.ConnectionState.Open) LogEntry(string.Format("error opening sql connection: {0}", _cs), LogLevels.Warning); return _conn; } catch (Exception E) { if (LogError) LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } public static async Task ExecuteAsync(cDbConnection Connection, cDbQuery Query, Dictionary Parameters, cF4sdWebRequestInfo requestInfo, CancellationToken Token, DbTransaction Transaction = null, bool LogNoResult = false, bool LogSilent = false, int Timeout = 0) { var _startTime = DateTime.UtcNow; if (Timeout == 0) Timeout = Connection.Connector.Timeout * Query.TimeoutFactor; var res = await ExecuteAsync(Connection.Connection, Query.Query, Parameters, Token, Timeout, Transaction, LogNoResult, LogSilent); if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, Parameters, 0, requestInfo?.requestName); return res; } public static async Task ExecuteAsync(DbConnection Connection, string Query, Dictionary Parameters, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null, bool LogNoResult = false, bool LogSilent = false) { MethodBase CM = null; if (!LogSilent) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } int? RetVal = null; try { using (DbCommand DbCmd = Connection.CreateCommand()) { if (Timeout > 0) DbCmd.CommandTimeout = Timeout; DbCmd.CommandText = Query; DbCmd.Transaction = Transaction; if (Parameters != null) { foreach (var Entry in Parameters) { DbParameter Param = DbCmd.CreateParameter(); Param.ParameterName = Entry.Key; if (Entry.Value == null) Param.Value = DBNull.Value; else Param.Value = Entry.Value; DbCmd.Parameters.Add(Param); } } RetVal = await DbCmd.ExecuteNonQueryAsync(Token); } } catch (Exception E) { LogException(E); LogSqlInfo("ExecuteAsync", Query, Parameters); } finally { if (CM != null) LogMethodEnd(CM); } return RetVal; } public static async Task GetDataReaderAsync(cDbConnection Connection, cDbQuery Query, Dictionary Parameters, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null, bool LogNoResult = false) { return await GetDataReaderAsync(Connection.Connection, Query.Query, Parameters, Token, Connection.Connector.Timeout * Query.TimeoutFactor, Transaction, LogNoResult); } public static async Task GetDataReaderAsync(DbConnection Connection, string Query, Dictionary Parameters, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null, bool LogNoResult = false) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { using (DbCommand DbCmd = Connection.CreateCommand()) { if (Timeout < 0) DbCmd.CommandTimeout = Timeout; DbCmd.CommandText = Query; DbCmd.Transaction = Transaction; if (Parameters != null) { foreach (var Entry in Parameters) { DbParameter Param = DbCmd.CreateParameter(); Param.ParameterName = Entry.Key; Param.Value = Entry.Value; DbCmd.Parameters.Add(Param); } } var RetVal = await DbCmd.ExecuteReaderAsync(Token); return RetVal; } } catch (Exception E) { LogException(E); LogSqlInfo("ExecuteAsync", Query, Parameters); } finally { if (CM != null) LogMethodEnd(CM); } return null; } public static async Task GetScalarResultAsync(cDbConnection Connection, cDbQuery Query, Dictionary Parameters, cF4sdWebRequestInfo requestInfo, CancellationToken Token, DbTransaction Transaction = null, bool LogNoResult = false) { var _startTime = DateTime.UtcNow; var res = await GetScalarResultAsync(Connection.Connection, Query.Query, Parameters, Token, Connection.Connector.Timeout * Query.TimeoutFactor, Transaction, LogNoResult); if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry(Query.Name, _startTime, Parameters, 0, requestInfo?.requestName); return res; } public static async Task GetScalarResultAsync(DbConnection Connection, string Query, Dictionary Parameters, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null, bool LogNoResult = false) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { using (DbCommand DbCmd = Connection.CreateCommand()) { if (Timeout < 0) DbCmd.CommandTimeout = Timeout; DbCmd.CommandText = Query; DbCmd.Transaction = Transaction; if (Parameters != null) { foreach (var Entry in Parameters) { DbParameter Param = DbCmd.CreateParameter(); Param.ParameterName = Entry.Key; Param.Value = Entry.Value; DbCmd.Parameters.Add(Param); } } var oRetVal = await DbCmd.ExecuteScalarAsync(Token); if (LogNoResult && (oRetVal == null || oRetVal is DBNull)) { LogEntry("No result (NULL) on SQL execute", LogLevels.Error); LogSqlInfo("GetScalarResultAsync", Query, Parameters); } if (oRetVal is T RetVal) return RetVal; } } catch (Exception E) { LogException(E); LogSqlInfo("GetScalarResultAsync", Query, Parameters); } finally { if (CM != null) LogMethodEnd(CM); } return default; } public static async Task GetTableResultAsync(cDbConnection Connection, cDbQuery Query, Dictionary Parameters, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null) { return await GetTableResultAsync(Connection.Connection, Query.Query, Parameters, Token, Connection.Connector.Timeout * Query.TimeoutFactor, Transaction); } public static async Task GetTableResultAsync(DbConnection Connection, string Query, Dictionary Parameters, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null, bool SupportCancellation = false) { DbDataReader RetVal = null; try { using (DbCommand DbCmd = Connection.CreateCommand()) { if (Timeout != 0) DbCmd.CommandTimeout = Timeout; DbCmd.CommandText = Query; DbCmd.Transaction = Transaction; if (Parameters != null) { foreach (var Entry in Parameters) { DbParameter Param = DbCmd.CreateParameter(); Param.ParameterName = Entry.Key; Param.Value = Entry.Value; DbCmd.Parameters.Add(Param); } } try { RetVal = await DbCmd.ExecuteReaderAsync(Token); } catch { if (!Token.IsCancellationRequested || !SupportCancellation) throw; else { DbCmd.Cancel(); return null; } } if (RetVal == null) { LogEntry("No result (NULL) on SQL execute", LogLevels.Error); LogSqlInfo("GetTableResult", Query, Parameters); } } } catch (Exception E) { if (E is TaskCanceledException) { } cLogManager.DefaultLogger.LogException(E); LogSqlInfo("GetTableResult", Query, Parameters); RetVal = null; } return RetVal; } public static async Task CheckIfTableExistAsync(DbConnection Connection, string TableName, cF4sdWebRequestInfo requestInfo, int LogDeep) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); } var _startTime = DateTime.UtcNow; try { var Query = "SELECT COUNT(OBJECT_ID(@TableName, N'U'))"; var Params = new Dictionary() { { "TableName", TableName } }; var Res = await GetScalarResultAsync(Connection, Query, Params, CancellationToken.None, LogNoResult: true); if (Res >= 1) return true; } catch (Exception E) { LogException(E); } finally { if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); } if (CM != null) LogMethodEnd(CM); } return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool CheckDbConnection(DbConnection Connection) { return (Connection != null && Connection.State == System.Data.ConnectionState.Open); } public static void LogSqlInfo(string Func, string Query, Dictionary Parameters) { var Msg = string.Format("DataHistorySqlHelper.{0}: Query={1}", Func, Query); if (Parameters != null) { var lstMsg = new List { Msg, "Parameters:" }; foreach (var Entry in Parameters) lstMsg.Add(Entry.Key + "=" + Entry.Value?.ToString()); cLogManager.DefaultLogger.LogList(LogLevels.Warning, lstMsg); } else cLogManager.DefaultLogger.LogEntry(LogLevels.Warning, Msg); } public static async Task> GetTableReferences(DbConnection Connection, string TableName, CancellationToken Token) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var Query = "SELECT fk.name name, tb.name tableName, cp.name tableColumn, tr.name referencedTable, cr.name referencedColumn FROM sys.foreign_keys fk"; Query += " INNER JOIN sys.tables tb ON tb.object_id = fk.parent_object_id"; Query += " INNER JOIN sys.tables tr ON tr.object_id = fk.referenced_object_id"; Query += " INNER JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id"; Query += " INNER JOIN sys.columns cp ON fkc.parent_column_id = cp.column_id AND fkc.parent_object_id = cp.object_id"; Query += " INNER JOIN sys.columns cr ON fkc.referenced_column_id = cr.column_id AND fkc.referenced_object_id = cr.object_id"; Query += " WHERE tb.name = @TableName"; var Params = new Dictionary() { { "TableName", TableName} }; var _res = new Dictionary(); using (var _reader = await GetDataReaderAsync(Connection, Query, Params, Token, LogNoResult: true)) { if (_reader.HasRows) { while (await _reader.ReadAsync()) { try { var _RefName = _reader.GetString(0); if (!_res.TryGetValue(_RefName, out var _if)) { _if = new cSqlReferenceInfo() { Table = _reader.GetString(1), ReferencedTable = _reader.GetString(3), Name = _RefName }; _res.Add(_RefName, _if); _if.Columns.Add(_reader.GetString(2)); _if.ReferencedColumns.Add(_reader.GetString(4)); } } catch (Exception E) { LogException(E); } finally { } } } } return _res; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } public static async Task getDbReaderHistoricTable(cDbConnection Conn, string Table, Dictionary Filter, DateTime LastEventTime, CancellationToken Token) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var strFilter = ""; int i = 0; var Params = new Dictionary(); foreach (var Entry in Filter) { i++; if (strFilter != "") strFilter += " AND "; strFilter += "t.[" + Entry.Key + "] = @P" + i.ToString(); Params.Add("P" + i.ToString(), Entry.Value); } Params.Add("LASTTIME", LastEventTime); var Cmd = $"SELECT h.[TimeFrameFrom], h.[TimeFrameTo], t.* FROM [{Table}] t JOIN [main-scan-history] h ON h.[ScanId] = t.[ScanId] WHERE {strFilter} AND h.[State] = 1 AND h.[TimeFrameFrom] >= @LASTTIME ORDER BY h.[TimeFrameTo] DESC"; var _reader = await GetDataReaderAsync(Conn.Connection, Cmd, Params, Token); return _reader; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } public static async Task getDbReaderSelectAsync(cDbConnection Conn, string Table, IEnumerable Columns, Dictionary Filter, CancellationToken Token, int Timeout = 0, DbTransaction Transaction = null) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var strFields = ""; foreach (var strField in Columns) { if (strFields != "") strFields += ","; strFields += "[" + strField + "]"; } var strFilter = ""; int i = 0; var Params = new Dictionary(); foreach (var Entry in Filter) { i++; if (strFilter != "") strFilter += " AND "; strFilter += "[" + Entry.Key + "] = @P" + i.ToString(); Params.Add("P" + i.ToString(), Entry.Value); } if (strFilter != "") strFilter = " WHERE " + strFilter; var Cmd = $"SELECT {strFields} FROM [{Table}]{strFilter}"; var Reader = await GetDataReaderAsync(Conn.Connection, Cmd, Params, Token, Timeout, Transaction); return Reader; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } public static async Task getDbReaderDetailTable(cDbConnection Conn, string Table, Dictionary Filter, string rowTime, int Age, CancellationToken Token) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { var strFilter = ""; int i = 0; var Params = new Dictionary(); foreach (var Entry in Filter) { i++; if (strFilter != "") strFilter += " AND "; strFilter += "[" + Entry.Key + "] = @P" + i.ToString(); Params.Add("P" + i.ToString(), Entry.Value); } Params.Add("AGE", Age); var Cmd = $"SELECT * FROM [{Table}] WHERE {strFilter} AND [{rowTime}] >= DATEADD(DAY,-@Age, GETUTCDATE()) ORDER BY [{rowTime}] DESC"; var _reader = await GetDataReaderAsync(Conn.Connection, Cmd, Params, Token); return _reader; } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } return null; } public static async Task DoUpdateOrInsertAsync(cDbConnection Connection, string Table, Dictionary Columns, List IDs, cF4sdWebRequestInfo requestInfo, int LogDeep, CancellationToken Token, DbTransaction Transaction = null, bool LogNoResult = false, bool LogSilent = false) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); } var _startTime = DateTime.UtcNow; try { string strSet = ""; string strWhere = ""; string strInsertCol = ""; string strInstertVal = ""; var SqlParams = new Dictionary(); int PNr = 0; foreach (var Entry in Columns) { PNr++; var PName = "P" + PNr.ToString(); var V = Entry.Value; if (V == null) V = DBNull.Value; SqlParams.Add(PName, V); if (strInsertCol != "") strInsertCol += ", "; strInsertCol += '[' + Entry.Key + ']'; if (strInstertVal != "") strInstertVal += ", "; strInstertVal += "@" + PName; if (IDs.Contains(Entry.Key)) { if (strWhere != "") strWhere += " AND "; strWhere += '[' + Entry.Key + "]=@" + PName; } else { if (strSet != "") strSet += ", "; strSet += '[' + Entry.Key + "]=@" + PName; } } var Query = $"UPDATE [{Table}] SET {strSet} WHERE {strWhere}\n"; Query += $"IF @@ROWCOUNT = 0 INSERT INTO [{Table}] ({strInsertCol}) VALUES ({strInstertVal})"; var n = await ExecuteAsync(Connection.Connection, Query, SqlParams, Token, Connection.Connector.Timeout * 10, LogSilent: LogSilent); if (n > 0) return true; } catch (Exception E) { LogException(E); } finally { if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry("DoUpdateOrInsertAsync", _startTime, Table, 0, requestInfo?.requestName); if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); } if (CM != null) LogMethodEnd(CM); } return false; } public static async Task DoInsertAsync(cDbConnection Connection, string Table, Dictionary Columns, CancellationToken Token, cF4sdWebRequestInfo requestInfo, int LogDeep, DbTransaction Transaction = null, bool LogNoResult = false, bool LogSilent = false) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } if (cPerformanceLogger.IsActive && requestInfo != null) { if (CM == null) CM = MethodBase.GetCurrentMethod(); cPerformanceLogger.LogPerformanceStart(LogDeep, CM, requestInfo.id, requestInfo.created); } var _startTime = DateTime.UtcNow; try { string strInsertCol = ""; string strInstertVal = ""; var SqlParams = new Dictionary(); int PNr = 0; foreach (var Entry in Columns) { PNr++; var PName = "P" + PNr.ToString(); var V = Entry.Value; if (V == null) V = DBNull.Value; SqlParams.Add(PName, V); if (strInsertCol != "") strInsertCol += ", "; strInsertCol += '[' + Entry.Key + ']'; if (strInstertVal != "") strInstertVal += ", "; strInstertVal += "@" + PName; } var Query = $"INSERT INTO [{Table}] ({strInsertCol}) VALUES ({strInstertVal})"; var n = await ExecuteAsync(Connection.Connection, Query, SqlParams, Token, Connection.Connector.Timeout * 10, LogSilent: LogSilent); if (n > 0) return true; } catch (Exception E) { LogException(E); } finally { if (DataHistorySqlHelper.LogSql) DataHistorySqlHelper.SaveSqlTimingEntry("DoInsertAsync", _startTime, Table, 0, requestInfo?.requestName); if (cPerformanceLogger.IsActive && requestInfo != null) { cPerformanceLogger.LogPerformanceEnd(LogDeep, CM, requestInfo.id, requestInfo.created, _startTime); } if (CM != null) LogMethodEnd(CM); } return false; } public static DateTime ConvertDBDateTimeToUTC(DateTime d) { return DateTime.SpecifyKind(d, DateTimeKind.Utc); } public static string GetSqlLogFileName() { try { if (!(cLogManager.Instance is cLogManagerFile lf)) return null; if (string.IsNullOrEmpty(lf.LogFolder)) return null; var _fn = Path.GetFileNameWithoutExtension(lf.GetLogFileName()); return Path.Combine(lf.LogFolder, _fn + "-SqlTiming.log"); } catch (Exception E) { LogException(E); } return null; } } public class cSqlReferenceInfo { public string Table; public string ReferencedTable; public string Name; public List Columns = new List(); public List ReferencedColumns = new List(); } public class cDbQuery { public string Query = ""; public string Name = ""; public int TimeoutFactor = 1; public int Subsequent = 0; public string Description = ""; public cDbQuery Clone(string _query = null) { var me = new cDbQuery() { Query = Query, Name = Name, TimeoutFactor = TimeoutFactor, Subsequent = Subsequent, Description = Description }; if (_query != null) me.Query = _query; return me; } } public class cDbConnection : IDisposable { public DbConnection Connection { get; private set; } = null; public cDataHistoryConfigSqlConnection Connector { get; private set; } = null; public bool IsOpen { get; private set; } = false; public cDbConnection(cDataHistoryConfigSqlConnection Connector, int? Timeout = null, bool MARS = false, bool WithoutDatabase = false, bool LogError = true) { MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); } try { this.Connector = Connector; int TO = Connector.Timeout; if (Timeout != null) TO = (int)Timeout; Connection = DataHistorySqlHelper.OpenConnection(Connector, TO, MARS, WithoutDatabase, LogError); IsOpen = DataHistorySqlHelper.CheckDbConnection(Connection); } catch (Exception E) { LogException(E); } finally { if (CM != null) LogMethodEnd(CM); } } public void Dispose() { if (Connection != null) { try { if (Connection.State == ConnectionState.Open) Connection.Close(); } catch { } ; try { Connection.Dispose(); } catch { } ; } } } }