Imports System.ComponentModel
Imports System.Data.SqlClient
Imports System.Threading
Imports DigitalData.Modules.Encryption
Imports DigitalData.Modules.Logging

Public Class MSSQLServer
    Implements IDatabase

    Public Property DBInitialized As Boolean = False Implements IDatabase.DBInitialized
    Public Property CurrentConnectionString As String = "" Implements IDatabase.CurrentConnectionString
    Public ReadOnly Property MaskedConnectionString As String
        Get
            If CurrentConnectionString = "" Then
                Return ""
            Else
                Return MaskConnectionString(CurrentConnectionString)
            End If
        End Get
    End Property

    Private ReadOnly QueryTimeout As Integer
    Private ReadOnly Logger As Logger

    Public Enum TransactionMode
        <Description("Use no transaction, neither internal nor external")>
        NoTransaction
        <Description("Use the transaction supplied in the Transaction Parameter")>
        ExternalTransaction
        <Description("Create an internal transaction on the fly")>
        WithTransaction
    End Enum

    Public Sub New(pLogConfig As LogConfig, pConnectionString As String, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT)
        Logger = pLogConfig.GetLogger()
        QueryTimeout = pTimeout

        Try
            CurrentConnectionString = pConnectionString
            DBInitialized = TestCanConnect(CurrentConnectionString)
        Catch ex As Exception
            DBInitialized = False
            Logger.Error(ex)
        End Try
    End Sub

    Public Sub New(pLogConfig As LogConfig, Server As String, Database As String, UserId As String, Password As String, Optional Timeout As Integer = Constants.DEFAULT_TIMEOUT)
        Logger = pLogConfig.GetLogger()
        QueryTimeout = Timeout

        Try
            CurrentConnectionString = GetConnectionString(Server, Database, UserId, Password)
            DBInitialized = TestCanConnect(CurrentConnectionString)
        Catch ex As Exception
            DBInitialized = False
            Logger.Error(ex)
        End Try
    End Sub

    ''' <summary>
    ''' Encrypts a connection string password.
    ''' </summary>
    ''' <param name="pConnectionString">A connection string with a plain-text password</param>
    ''' <returns>The connection string with the password encrypted.</returns>
    <DebuggerStepThrough()>
    Public Shared Function EncryptConnectionString(pConnectionString As String) As String
        Dim oEncryption As New EncryptionLegacy()
        Dim oBuilder As New SqlConnectionStringBuilder() With {.ConnectionString = pConnectionString}
        Dim oEncryptedPassword = oEncryption.EncryptData(oBuilder.Password)
        oBuilder.Password = oEncryptedPassword

        Return oBuilder.ToString()
    End Function

    ''' <summary>
    ''' Decrypts a connection string password.
    ''' </summary>
    ''' <param name="pConnectionString">A connection string with a encrypted password</param>
    ''' <returns>The connection string with the password decrypted.</returns>
    <DebuggerStepThrough()>
    Public Shared Function DecryptConnectionString(pConnectionString As String) As String
        Dim oEncryption As New EncryptionLegacy()
        Dim oBuilder As New SqlConnectionStringBuilder() With {.ConnectionString = pConnectionString}
        Dim oDecryptedPassword = oEncryption.DecryptData(oBuilder.Password)
        oBuilder.Password = oDecryptedPassword

        Return oBuilder.ToString()
    End Function

    <DebuggerStepThrough()>
    Public Function GetConnectionString(Server As String, Database As String, UserId As String, Password As String) As String
        Dim oConnectionStringBuilder As New SqlConnectionStringBuilder() With {
            .DataSource = Server,
            .InitialCatalog = Database,
            .UserID = UserId,
            .Password = Password
        }

        Return oConnectionStringBuilder.ToString
    End Function

    <DebuggerStepThrough()>
    Public Function GetConnection() As SqlConnection
        Try
            Dim oConnection = GetSQLConnection()
            Return oConnection
        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing
        End Try
    End Function

    Private Function MaybeGetTransaction(pConnection As SqlConnection, pTransactionMode As TransactionMode, pTransaction As SqlTransaction) As SqlTransaction
        If pConnection Is Nothing Then
            Throw New ArgumentNullException("Connection", "Could not get transaction because connection is null!")
        End If

        Logger.Debug("Transaction Mode: [{0}]", pTransactionMode.ToString)

        If pTransactionMode = TransactionMode.NoTransaction Then
            Return Nothing
        ElseIf pTransactionMode = TransactionMode.ExternalTransaction Then
            Return pTransaction
        Else
            Return pConnection.BeginTransaction()
        End If
    End Function

    Private Function MaybeCommitTransaction(pTransaction As SqlTransaction, pTransactionMode As TransactionMode) As Boolean
        Select Case pTransactionMode
            Case TransactionMode.NoTransaction
                Return True
            Case TransactionMode.ExternalTransaction
                Return True
            Case TransactionMode.WithTransaction
                Try
                    pTransaction.Commit()
                    Return True
                Catch ex As Exception
                    Logger.Warn("Error while committing transaction!")
                    Logger.Error(ex)
                    Return False
                End Try
            Case Else
                Return True
        End Select
    End Function

    Public Function GetConnectionStringForId(pConnectionId As Integer) As String
        Return Get_ConnectionStringforID(pConnectionId)
    End Function
    Public Function GetGDPictureString() As String
        Using oConnection As SqlConnection = GetSQLConnection()
            Dim oSQL = Queries.DD_ECM.ThirdPartyModules.GdPictureLicense
            Dim oGDPicture = GetScalarValueWithConnectionObject(oSQL, oConnection)
            If oGDPicture = String.Empty Then
                oSQL = Queries.DD_ECM.ThirdPartyModules.GdPictureLicense_REGULAR
                oGDPicture = GetScalarValueWithConnectionObject(oSQL, oConnection)
            End If
            Return oGDPicture
        End Using
    End Function
    Public Function GetDDCatalog() As DataTable
        Using oConnection As SqlConnection = GetSQLConnection()
            Dim oSQL = Queries.DD_ECM.DD_SELECTS.TBDD_CATALOG
            Dim oDT_CATALOG As DataTable = GetDatatable(oSQL)
            Return oDT_CATALOG
        End Using
    End Function
    Public Function Get_ConnectionStringforID(pConnectionId As Integer) As String
        Dim oConnectionString As String = String.Empty

        Logger.Debug("Getting ConnectionString for ConnectionId [{0}]", pConnectionId)

        If pConnectionId = 0 Then
            Logger.Warn("ConnectionId was 0. Falling back to default connection.")
            Return String.Empty
        End If

        Try
            Dim oTable As DataTable = GetDatatable($"SELECT * FROM TBDD_CONNECTION WHERE GUID = {pConnectionId}")
            If oTable.Rows.Count = 1 Then
                Dim oRow As DataRow = oTable.Rows(0)
                Dim oProvider = oRow.Item("SQL_PROVIDER").ToString.ToUpper
                Dim oServer = oRow.Item("SERVER")
                Dim oDatabase = oRow.Item("DATENBANK")
                Dim oUser = oRow.Item("USERNAME")
                Dim oPassword = oRow.Item("PASSWORD")

                Select Case oProvider
                    Case "MS-SQL"
                        If oUser = "WINAUTH" Then
                            oConnectionString = $"Server={oServer};Database={oDatabase};Trusted_Connection=True;"
                        Else
                            oConnectionString = $"Server={oServer};Database={oDatabase};User Id={oUser};Password={oPassword};"
                        End If

                    Case "ORACLE"
                        If oRow.Item("BEMERKUNG").ToString.Contains("without tnsnames") Then
                            oConnectionString = $"Data Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST={oServer})(PORT=1521)))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME={oDatabase})));User Id={oUser};Password={oPassword};"
                        Else
                            oConnectionString = $"Data Source={oServer};Persist Security Info=True;User Id={oUser};Password={oPassword};Unicode=True"
                        End If

                    Case Else
                        Logger.Warn("Provider [{0}] not supported!", oProvider)

                End Select

            Else
                Logger.Warn("No entry for Connection-ID: [{0}] ", pConnectionId.ToString)
            End If

        Catch ex As Exception
            Logger.Error(ex)
            Logger.Warn("Error in Get_ConnectionStringforID")
        End Try

        Return DecryptConnectionString(oConnectionString)
    End Function

    <DebuggerStepThrough()>
    Private Function TestCanConnect() As Boolean
        Return TestCanConnect(CurrentConnectionString)
    End Function

    Private Function TestCanConnect(pConnectionString As String) As Boolean
        Try
            Logger.Debug("Testing connection to [{0}]", MaskConnectionString(pConnectionString))

            Dim oDecryptedConnectionString = DecryptConnectionString(pConnectionString)
            Dim oConnection As New SqlConnection(oDecryptedConnectionString)
            OpenSQLConnection(oConnection)
            oConnection?.Close()
            Return True

        Catch ex As Exception
            Logger.Error("Error while testing connection!")
            Logger.Error(ex)

            Return False
        End Try
    End Function

    ''' <summary>
    ''' This Function intentionally has no try..catch block to have any errors caught outside
    ''' </summary>
    ''' <param name="pConnection"></param>
    ''' <returns></returns>
    Private Function OpenSQLConnection(pConnection As SqlConnection) As SqlConnection
        Try
            If pConnection.State = ConnectionState.Closed Then
                pConnection.Open()
            End If

            Return pConnection
        Catch ex As Exception
            Logger.Error("Error while opening Connection!")
            Logger.Error(ex)

            Throw ex
        End Try
    End Function

    <DebuggerStepThrough()>
    Private Function GetSQLConnection() As SqlConnection
        Return GetConnection(CurrentConnectionString)
    End Function

    Private Function GetConnection(pConnectionString As String) As SqlConnection
        Try
            Dim oConnection As New SqlConnection(pConnectionString)
            oConnection = OpenSQLConnection(oConnection)

            Dim oMaskedConnectionString = MaskConnectionString(pConnectionString)
            Logger.Debug("The Following Connection is open: {0}", oMaskedConnectionString)

            Return oConnection
        Catch ex As Exception
            Logger.Error("Connection could not be created or opened!")
            Logger.Error(ex)

            Return Nothing
        End Try
    End Function

    <DebuggerStepThrough()>
    Private Function MaskConnectionString(pConnectionString As String) As String
        Try
            If pConnectionString Is Nothing OrElse pConnectionString.Length = 0 Then
                Logger.Warn("Connection String is empty!")
                Throw New ArgumentNullException("pConnectionString", "Could not mask connection string because connectiong string is empty!")
            End If

            Dim oBuilder As New SqlConnectionStringBuilder() With {.ConnectionString = pConnectionString}
            Dim oConnectionString = pConnectionString.Replace(oBuilder.Password, "XXXXX")
            Return oConnectionString
        Catch ex As Exception
            Logger.Error("ConnectionString is invalid and could not be masked!")
            Logger.Error(ex)

            Return "Invalid ConnectionString"
        End Try
    End Function

    ''' <summary>
    ''' Returns a Datatable for a SQL Statement 
    ''' </summary>
    ''' <param name="pSqlCommand">SQL Command Text for Datatable (select XYZ from TableORView)</param>
    ''' <returns>A datatable</returns>
    Public Function GetDatatable(pSqlCommand As String, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As DataTable Implements IDatabase.GetDatatable
        Using oSqlConnection = GetSQLConnection()
            Return GetDatatableWithConnectionObject(pSqlCommand, oSqlConnection, TransactionMode.WithTransaction, Nothing, pTimeout)
        End Using
    End Function

    Public Function GetDatatableWithoutTransaction(pSqlCommand As String, Optional pTimeout As Integer = 120) As DataTable Implements IDatabase.GetDatatableWithoutTransaction
        Using oSqlConnection = GetSQLConnection()
            Return GetDatatableWithConnectionObject(pSqlCommand, oSqlConnection, TransactionMode.NoTransaction, Nothing, pTimeout)
        End Using

    End Function

    Public Function GetDatatableWithoutTransaction(pSqlCommand As SqlCommand, Optional pTimeout As Integer = 120) As DataTable Implements IDatabase.GetDatatableWithoutTransaction
        Using oSqlConnection = GetSQLConnection()
            Return GetDatatableWithConnectionObject(pSqlCommand, oSqlConnection, TransactionMode.NoTransaction, Nothing, pTimeout)
        End Using
    End Function

    ''' <summary>
    ''' Returns a datatable for a SQL Statement 
    ''' </summary>
    ''' <param name="pSqlCommandObject">SQL Command Object for Datatable (select XYZ from TableORView)</param>
    ''' <returns>A datatable</returns>
    Public Function GetDatatable(pSqlCommandObject As SqlCommand, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As DataTable Implements IDatabase.GetDatatable
        Using oSqlConnection = GetSQLConnection()
            Return GetDatatableWithConnectionObject(pSqlCommandObject, oSqlConnection, TransactionMode.WithTransaction, Nothing, pTimeout)
        End Using
    End Function

    Public Function GetDatatable(pSqlCommand As String, pTransaction As SqlTransaction, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As DataTable
        Return GetDatatableWithConnectionObject(pSqlCommand, pTransaction.Connection, TransactionMode.ExternalTransaction, pTransaction, pTimeout)
    End Function

    Public Function GetDatatable(pSqlCommandObject As SqlCommand, pTransaction As SqlTransaction, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As DataTable
        Return GetDatatableWithConnectionObject(pSqlCommandObject, pTransaction.Connection, TransactionMode.ExternalTransaction, pTransaction, pTimeout)
    End Function

    Public Async Function GetDatatableAsync(pSqlCommand As String, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Task(Of DataTable)
        Return Await Task.Run(Function() GetDatatable(pSqlCommand, pTimeout))
    End Function

    Public Async Function GetDatatableAsync(pSqlCommandObject As SqlCommand, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Task(Of DataTable)
        Return Await Task.Run(Function() GetDatatable(pSqlCommandObject, pTimeout))
    End Function

    Public Async Function GetDatatableWithoutTransactionAsync(pSqlCommand As String, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Task(Of DataTable)
        Return Await Task.Run(Function() GetDatatableWithoutTransaction(pSqlCommand, pTimeout))
    End Function

    Public Function GetDatatableWithConnection(pSqlCommand As String, pConnectionString As String, Optional Timeout As Integer = Constants.DEFAULT_TIMEOUT) As DataTable
        Using oConnection = GetConnection(pConnectionString)
            Return GetDatatableWithConnectionObject(pSqlCommand, oConnection, pTimeout:=Timeout)
        End Using
    End Function

    Public Function GetDatatableWithConnection(pSqlCommandObject As SqlCommand, pConnectionString As String, Optional Timeout As Integer = Constants.DEFAULT_TIMEOUT) As DataTable
        Using oConnection = GetConnection(pConnectionString)
            Return GetDatatableWithConnectionObject(pSqlCommandObject, oConnection, pTimeout:=Timeout)
        End Using
    End Function

    Public Async Function GetDatatableWithConnectionObjectAsync(pSqlCommand As String, pSqlConnection As SqlConnection,
                                                     Optional pTransactionMode As TransactionMode = TransactionMode.WithTransaction,
                                                     Optional pTransaction As SqlTransaction = Nothing,
                                                     Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Task(Of DataTable)
        Return Await Task.Run(Function() GetDatatableWithConnectionObject(pSqlCommand, pSqlConnection, pTransactionMode, pTransaction, pTimeout))
    End Function

    Public Async Function GetDatatableWithConnectionObjectAsync(pSqlCommandObject As SqlCommand, pSqlConnection As SqlConnection,
                                                     Optional pTransactionMode As TransactionMode = TransactionMode.WithTransaction,
                                                     Optional pTransaction As SqlTransaction = Nothing,
                                                     Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Task(Of DataTable)
        Return Await Task.Run(Function() GetDatatableWithConnectionObject(pSqlCommandObject, pSqlConnection, pTransactionMode, pTransaction, pTimeout))
    End Function

    Public Function GetDatatableWithConnectionObject(pSqlCommand As String, pSqlConnection As SqlConnection,
                                                     Optional pTransactionMode As TransactionMode = TransactionMode.WithTransaction,
                                                     Optional pTransaction As SqlTransaction = Nothing,
                                                     Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As DataTable
        Dim oSQLCommand As New SqlCommand(pSqlCommand)
        Return GetDatatableWithConnectionObject(oSQLCommand, pSqlConnection, pTransactionMode, pTransaction, pTimeout)
    End Function

    Public Function GetDatatableWithConnectionObject(pSqlCommandObject As SqlCommand, pSqlConnection As SqlConnection,
                                                     Optional pTransactionMode As TransactionMode = TransactionMode.WithTransaction,
                                                     Optional pTransaction As SqlTransaction = Nothing,
                                                     Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As DataTable
        Dim oTransaction As SqlTransaction = MaybeGetTransaction(pSqlConnection, pTransactionMode, pTransaction)
        Dim oTable As New DataTable() With {.TableName = Constants.DEFAULT_TABLE}

        Try
            pSqlCommandObject.Connection = pSqlConnection
            pSqlCommandObject.Transaction = oTransaction
            pSqlCommandObject.CommandTimeout = pTimeout

            Using oAdapter As New SqlDataAdapter(pSqlCommandObject)
                Logger.Debug("GetDatatableWithConnectionObject: Running Query [{0}] and Parameters [{1}]", pSqlCommandObject.CommandText, GetParameterListAsString(pSqlCommandObject))
                oAdapter.Fill(oTable)
            End Using

        Catch ex As Exception
            Logger.Error("GetDatatableWithConnectionObject: Error in GetDatatableWithConnection while executing command: [{0}]", pSqlCommandObject.CommandText)
            Logger.Error(ex)
            Throw ex
        Finally
            MaybeCommitTransaction(oTransaction, pTransactionMode)
        End Try

        Return oTable
    End Function

    Public Function ExecuteNonQuery(pSQLCommand As String) As Boolean Implements IDatabase.ExecuteNonQuery
        Using oConnection = GetSQLConnection()
            Return ExecuteNonQueryWithConnectionObject(pSQLCommand, oConnection, TransactionMode.WithTransaction, Nothing, QueryTimeout)
        End Using
    End Function

    Public Function ExecuteNonQuery(pSQLCommandObject As SqlCommand) As Boolean
        Using oConnection = GetSQLConnection()
            Return ExecuteNonQueryWithConnectionObject(pSQLCommandObject, oConnection, TransactionMode.WithTransaction, Nothing, QueryTimeout)
        End Using
    End Function

    Public Function ExecuteNonQuery(pSQLCommand As String, pTimeout As Integer) As Boolean Implements IDatabase.ExecuteNonQuery
        Using oConnection = GetSQLConnection()
            Return ExecuteNonQueryWithConnectionObject(pSQLCommand, oConnection, TransactionMode.WithTransaction, Nothing, pTimeout)
        End Using
    End Function

    Public Function ExecuteNonQuery(pSQLCommandObject As SqlCommand, pTimeout As Integer) As Boolean
        Using oConnection = GetSQLConnection()
            Return ExecuteNonQueryWithConnectionObject(pSQLCommandObject, oConnection, TransactionMode.WithTransaction, Nothing, pTimeout)
        End Using
    End Function


    Public Function ExecuteNonQuery(pSQLCommand As String, pTransaction As SqlTransaction, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Boolean
        Return ExecuteNonQueryWithConnectionObject(pSQLCommand, pTransaction.Connection, TransactionMode.ExternalTransaction, pTransaction, pTimeout)
    End Function

    Public Function ExecuteNonQuery(pSQLCommandObject As SqlCommand, pTransaction As SqlTransaction, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Boolean
        Return ExecuteNonQueryWithConnectionObject(pSQLCommandObject, pTransaction.Connection, TransactionMode.ExternalTransaction, pTransaction, pTimeout)
    End Function

    Public Async Function ExecuteNonQueryAsync(pSQLCommand As String, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Task(Of Boolean)
        Return Await Task.Run(Function() ExecuteNonQuery(pSQLCommand, pTimeout))
    End Function

    Public Async Function ExecuteNonQueryAsync(pSQLCommandObject As SqlCommand, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Task(Of Boolean)
        Return Await Task.Run(Function() ExecuteNonQuery(pSQLCommandObject, pTimeout))
    End Function

    Public Function ExecuteNonQueryWithConnection(pSQLCommand As String, ConnString As String, Optional Timeout As Integer = Constants.DEFAULT_TIMEOUT) As Boolean
        Using oConnection = GetConnection(ConnString)
            Return ExecuteNonQueryWithConnectionObject(pSQLCommand, oConnection, TransactionMode.WithTransaction, Nothing, Timeout)
        End Using
    End Function

    Public Function ExecuteNonQueryWithConnection(pSQLCommandObject As SqlCommand, ConnString As String, Optional Timeout As Integer = Constants.DEFAULT_TIMEOUT) As Boolean
        Using oConnection = GetConnection(ConnString)
            Return ExecuteNonQueryWithConnectionObject(pSQLCommandObject, oConnection, TransactionMode.WithTransaction, Nothing, Timeout)
        End Using
    End Function

    Public Function ExecuteNonQueryWithConnectionObject(pSqlCommand As String, pSqlConnection As SqlConnection,
                                                        Optional pTransactionMode As TransactionMode = TransactionMode.WithTransaction,
                                                        Optional pTransaction As SqlTransaction = Nothing,
                                                        Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Boolean
        Dim oSQLCommand As New SqlCommand(pSqlCommand)
        Return ExecuteNonQueryWithConnectionObject(oSQLCommand, pSqlConnection, pTransactionMode, pTransaction, pTimeout)
    End Function

    Public Function ExecuteNonQueryWithConnectionObject(pSqlCommandObject As SqlCommand, pSqlConnection As SqlConnection,
                                                        Optional pTransactionMode As TransactionMode = TransactionMode.WithTransaction,
                                                        Optional pTransaction As SqlTransaction = Nothing,
                                                        Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Boolean
        Dim oTransaction As SqlTransaction = MaybeGetTransaction(pSqlConnection, pTransactionMode, pTransaction)

        Try
            Logger.Debug("ExecuteNonQueryWithConnectionObject: Running Command [{0}] and Parameters [{1}]", pSqlCommandObject.CommandText, GetParameterListAsString(pSqlCommandObject))

            pSqlCommandObject.Connection = pSqlConnection
            pSqlCommandObject.Transaction = oTransaction
            pSqlCommandObject.CommandTimeout = pTimeout
            pSqlCommandObject.ExecuteNonQuery()

            Return True
        Catch ex As Exception
            Dim omsg = $"Unexpected Error in ExecuteNonQueryWithConnectionObject while executing command: [{pSqlCommandObject.CommandText}]"
            Logger.Warn(omsg)
            Logger.Error(ex)

            Return False
        Finally
            MaybeCommitTransaction(oTransaction, pTransactionMode)
        End Try
    End Function

    Public Function GetScalarValue(pSqlQuery As String) As Object Implements IDatabase.GetScalarValue
        Using oConnection As SqlConnection = GetSQLConnection()
            Return GetScalarValueWithConnectionObject(pSqlQuery, oConnection)
        End Using
    End Function

    Public Function GetScalarValue(pSqlCommandObject As SqlCommand) As Object
        Using oConnection As SqlConnection = GetSQLConnection()
            Return GetScalarValueWithConnectionObject(pSqlCommandObject, oConnection)
        End Using
    End Function

    Public Function GetScalarValue(pSqlCommand As String, pTimeout As Integer) As Object Implements IDatabase.GetScalarValue
        Using oConnection = GetSQLConnection()
            Return GetScalarValueWithConnectionObject(pSqlCommand, oConnection, TransactionMode.WithTransaction, Nothing, pTimeout)
        End Using
    End Function

    Public Function GetScalarValue(pSqlCommandObject As SqlCommand, pTimeout As Integer) As Object
        Using oConnection = GetSQLConnection()
            Return GetScalarValueWithConnectionObject(pSqlCommandObject, oConnection, TransactionMode.WithTransaction, Nothing, pTimeout)
        End Using
    End Function

    Public Function GetScalarValue(pSQLCommand As String, pTransaction As SqlTransaction, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Object
        Return GetScalarValueWithConnectionObject(pSQLCommand, pTransaction.Connection, TransactionMode.ExternalTransaction, pTransaction, pTimeout)
    End Function

    Public Function GetScalarValue(pSQLCommandObject As SqlCommand, pTransaction As SqlTransaction, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Object
        Return GetScalarValueWithConnectionObject(pSQLCommandObject, pTransaction.Connection, TransactionMode.ExternalTransaction, pTransaction, pTimeout)
    End Function

    Public Async Function GetScalarValueAsync(pSQLCommand As String, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Task(Of Object)
        Return Await Task.Run(Function() GetScalarValue(pSQLCommand, pTimeout))
    End Function

    Public Async Function GetScalarValueAsync(pSQLCommandObject As SqlCommand, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Task(Of Object)
        Return Await Task.Run(Function() GetScalarValue(pSQLCommandObject, pTimeout))
    End Function

    Public Function GetScalarValueWithConnection(pSQLCommand As String, pConnectionString As String, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Object
        Using oConnection = GetConnection(pConnectionString)
            Return GetScalarValueWithConnectionObject(pSQLCommand, oConnection, TransactionMode.WithTransaction, Nothing, pTimeout)
        End Using
    End Function

    Public Function GetScalarValueWithConnection(pSqlCommandObject As SqlCommand, pConnectionString As String, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Object
        Using oConnection = GetConnection(pConnectionString)
            Return GetScalarValueWithConnectionObject(pSqlCommandObject, oConnection, TransactionMode.WithTransaction, Nothing, pTimeout)
        End Using
    End Function

    Public Function GetScalarValueWithConnectionObject(pSqlCommand As String, pSqlConnection As SqlConnection,
                                                       Optional pTransactionMode As TransactionMode = TransactionMode.WithTransaction,
                                                       Optional pTransaction As SqlTransaction = Nothing,
                                                       Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Object
        Dim oSQLCommand As New SqlCommand(pSqlCommand)
        Return GetScalarValueWithConnectionObject(oSQLCommand, pSqlConnection, pTransactionMode, pTransaction, pTimeout)
    End Function

    Public Function GetScalarValueWithConnectionObject(pSqlCommandObject As SqlCommand, pSqlConnection As SqlConnection,
                                                       Optional pTransactionMode As TransactionMode = TransactionMode.WithTransaction,
                                                       Optional pTransaction As SqlTransaction = Nothing,
                                                       Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Object

        Dim oTransaction As SqlTransaction = MaybeGetTransaction(pSqlConnection, pTransactionMode, pTransaction)
        Dim oResult As Object = Nothing

        Try

            Logger.Debug("GetScalarValueWithConnectionObject: Running Query [{0}] with Parameters [{1}]", pSqlCommandObject.CommandText, GetParameterListAsString(pSqlCommandObject))

            pSqlCommandObject.Connection = pSqlConnection
            pSqlCommandObject.CommandTimeout = pTimeout
            pSqlCommandObject.Transaction = oTransaction
            oResult = pSqlCommandObject.ExecuteScalar()

        Catch ex As Exception
            Logger.Error(ex)
            Logger.Error("GetDatatableWithConnectionObject: Error in GetDatatableWithConnection while executing command: [{0}]", pSqlCommandObject.CommandText)

        Finally
            MaybeCommitTransaction(oTransaction, pTransactionMode)
        End Try

        Return oResult
    End Function

    Public Function GetScalarValue(pSqlCommand As SqlCommand, pOutputParameter As String, Optional pTimeout As Integer = Constants.DEFAULT_TIMEOUT) As Object
        Try
            If TestCanConnect() = False Then
                Return Nothing
            End If

            Logger.Debug("GetScalarValue: Running Query [{0}]", pSqlCommand)

            If pSqlCommand.CommandText.Contains(" ") Then
                pSqlCommand.CommandType = CommandType.Text
            Else
                pSqlCommand.CommandType = CommandType.StoredProcedure
            End If

            Using oConnection As SqlConnection = GetSQLConnection()

                pSqlCommand.Connection = oConnection
                pSqlCommand.Parameters(pOutputParameter).Direction = ParameterDirection.Output
                pSqlCommand.CommandTimeout = pTimeout
                pSqlCommand.ExecuteNonQuery()
                oConnection.Close()

                Return pSqlCommand.Parameters(pOutputParameter).Value
            End Using
        Catch ex As Exception
            Logger.Error(ex)
            Logger.Error($"GetScalarValue failed SQLCommand [{pSqlCommand}]")

            Return Nothing
        End Try
    End Function

    '<DebuggerStepThrough()>
    Public Function GetScalarValue(SQLCommand As SqlCommand, OutputParameter As String) As Object
        Return GetScalarValue(SQLCommand, OutputParameter, QueryTimeout)
    End Function

    ''' <summary>
    ''' Executes the passed sql-statement in asyncmode
    ''' </summary>
    ''' <param name="SqlCommand">the sql statement</param>
    ''' <param name="commandtimeout">Optional Timeout</param>
    ''' <remarks></remarks>
    Public Sub NewExecuteNonQueryAsync(SqlCommand As String, Optional commandtimeout As Integer = Constants.DEFAULT_TIMEOUT)
        Logger.Debug("NewExecuteNonQueryAsync: Running Query [{0}]", SqlCommand)

        Try
            Dim oCallback As New AsyncCallback(AddressOf NewExecuteNonQueryAsync_Callback)

            Using oConnection As SqlConnection = GetSQLConnection()
                Using oSQLCOmmand = oConnection.CreateCommand()
                    oSQLCOmmand.CommandText = SqlCommand
                    oSQLCOmmand.CommandTimeout = commandtimeout
                    oSQLCOmmand.BeginExecuteNonQuery(oCallback, oSQLCOmmand)
                End Using
            End Using
        Catch ex As Exception
            Logger.Error(ex)
            Logger.Error($"NewExecuteNonQueryAsync failed SQLCommand [{SqlCommand}]")

        End Try
    End Sub

    Private Sub NewExecuteNonQueryAsync_Callback(ByVal result As IAsyncResult)
        Dim command As SqlCommand = CType(result.AsyncState, SqlCommand)
        Dim res = command.EndExecuteNonQuery(result)
        Logger.Info("Finished executing Async database operation: {0}", command.CommandText)
    End Sub

    Private Function GetParameterListAsString(pSQLCommand As SqlCommand) As String
        Dim oList = pSQLCommand.Parameters.
            Cast(Of SqlParameter).
            Select(Function(p) $"({p.ParameterName}={p.Value})").
            ToList()

        Dim oParamString = String.Join(",", oList)
        If oParamString.Length > 1000 Then
            oParamString = oParamString.Substring(1, 1000)
        End If
        Return oParamString

    End Function

End Class
