Files
C4IT-F4SD-Collector/F4SD-Cockpit-ServerCore/DataHistorySqlHelper.cs
2025-11-11 11:12:05 +01:00

1668 lines
64 KiB
C#

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<string, cDbQuery> wellDefinedSqlStatements = new Dictionary<string, cDbQuery>();
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<string, Object> 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<string, Object> 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<cXmlParserNodeMessage> ParsingMessages, Assembly ExecutingAssembly = null)
{
MethodBase CM = null; if (cLogManager.DefaultLogger.IsDebug) { CM = MethodBase.GetCurrentMethod(); LogMethodBegin(CM); }
ParsingMessages = new List<cXmlParserNodeMessage>();
cXmlParser Parser = null;
try
{
wellDefinedSqlStatements.Clear();
var Locations = new List<cXmlFileLocation>() {
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 <query Name=\"{Q.Name}\">");
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<int?> ExecuteAsync(cDbConnection Connection, cDbQuery Query, Dictionary<string, Object> 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<int?> ExecuteAsync(DbConnection Connection, string Query, Dictionary<string, Object> 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<DbDataReader> GetDataReaderAsync(cDbConnection Connection, cDbQuery Query, Dictionary<string, Object> 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<DbDataReader> GetDataReaderAsync(DbConnection Connection, string Query, Dictionary<string, Object> 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<T> GetScalarResultAsync<T>(cDbConnection Connection, cDbQuery Query, Dictionary<string, Object> Parameters, cF4sdWebRequestInfo requestInfo, CancellationToken Token, DbTransaction Transaction = null, bool LogNoResult = false)
{
var _startTime = DateTime.UtcNow;
var res = await GetScalarResultAsync<T>(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<T> GetScalarResultAsync<T>(DbConnection Connection, string Query, Dictionary<string, Object> 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<DbDataReader> GetTableResultAsync(cDbConnection Connection, cDbQuery Query, Dictionary<string, Object> 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<DbDataReader> GetTableResultAsync(DbConnection Connection, string Query, Dictionary<string, Object> 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<bool> 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<string, Object>() {
{ "TableName", TableName }
};
var Res = await GetScalarResultAsync<int>(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<string, Object> Parameters)
{
var Msg = string.Format("DataHistorySqlHelper.{0}: Query={1}", Func, Query);
if (Parameters != null)
{
var lstMsg = new List<string>
{
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<Dictionary<string, cSqlReferenceInfo>> 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<string, object>()
{
{ "TableName", TableName}
};
var _res = new Dictionary<string, cSqlReferenceInfo>();
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<DbDataReader> getDbReaderHistoricTable(cDbConnection Conn, string Table, Dictionary<string, object> 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<string, object>();
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<DbDataReader> getDbReaderSelectAsync(cDbConnection Conn, string Table, IEnumerable<string> Columns, Dictionary<string, object> 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<string, object>();
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<DbDataReader> getDbReaderDetailTable(cDbConnection Conn, string Table, Dictionary<string, object> 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<string, object>();
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<bool> DoUpdateOrInsertAsync(cDbConnection Connection, string Table, Dictionary<string, object> Columns, List<string> 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<string, object>();
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<bool> DoInsertAsync(cDbConnection Connection, string Table, Dictionary<string, object> 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<string, object>();
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<string> Columns = new List<string>();
public List<string> ReferencedColumns = new List<string>();
}
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 { }
;
}
}
}
}