|
| 1 | +Imports Desharp |
| 2 | +Imports System.Configuration |
| 3 | +Imports System.Data.SqlClient |
| 4 | +Imports MySql.Data.MySqlClient |
| 5 | +Imports System.Data.Common |
| 6 | +Imports System.Reflection |
| 7 | +Imports System.Threading |
| 8 | + |
| 9 | +Public Class Connection |
| 10 | + Protected Shared supportedProviders As New List(Of String) From { |
| 11 | + "System.Data.SqlClient", |
| 12 | + "MySql.Data.MySqlClient" |
| 13 | + } |
| 14 | + Protected Shared config As New Dictionary(Of Int16, String()) |
| 15 | + Protected Shared namesAndIndexes As New Dictionary(Of String, Int32) |
| 16 | + Protected Shared connections As New Dictionary(Of String, Dictionary(Of Int16, DbConnection)) |
| 17 | + Protected Shared threadLock As New Object() |
| 18 | + ''' <summary> |
| 19 | + ''' Load config and set up connection strings. |
| 20 | + ''' </summary> |
| 21 | + Shared Sub New() |
| 22 | + If (Connection.config.Count = 0) Then |
| 23 | + Dim config As ConnectionStringsSection = DirectCast( |
| 24 | + ConfigurationSettings.GetConfig("connectionStrings"), |
| 25 | + ConnectionStringsSection |
| 26 | + ) |
| 27 | + Dim i As Int16 = 0 |
| 28 | + For Each cfgItem As ConnectionStringSettings In config.ConnectionStrings |
| 29 | + If Not Connection.supportedProviders.Contains(cfgItem.ProviderName) Then |
| 30 | + Throw New Exception($"This DB engine is not supperted: '{cfgItem.ProviderName}'.") |
| 31 | + End If |
| 32 | + Connection.config.Add( |
| 33 | + i, New String() {cfgItem.ProviderName, cfgItem.ConnectionString} |
| 34 | + ) |
| 35 | + Connection.namesAndIndexes.Add(cfgItem.Name, i) |
| 36 | + i += 1 |
| 37 | + Next |
| 38 | + End If |
| 39 | + End Sub |
| 40 | + ''' <summary> |
| 41 | + ''' Get and open connection by config index. |
| 42 | + ''' </summary> |
| 43 | + ''' <param name="connectionIndex">Config connection index.</param> |
| 44 | + ''' <returns></returns> |
| 45 | + Public Shared Function [Get](connectionIndex As Int32) As DbConnection |
| 46 | + If Not Connection.config.ContainsKey(connectionIndex) Then |
| 47 | + Throw New Exception($"Connection settings under index doesn't exist: {connectionIndex}.") |
| 48 | + End If |
| 49 | + Dim conn As DbConnection |
| 50 | + Dim processAndThreadKey As String = Connection.getProcessAndThreadKey() |
| 51 | + If ( |
| 52 | + Connection.connections.ContainsKey(processAndThreadKey) AndAlso |
| 53 | + Connection.connections(processAndThreadKey).ContainsKey(connectionIndex) |
| 54 | + ) Then |
| 55 | + conn = Connection.connections(processAndThreadKey).Item(connectionIndex) |
| 56 | + Else |
| 57 | + Dim typeAndDsn As String() = Connection.config(connectionIndex) |
| 58 | + If typeAndDsn(0) = "System.Data.SqlClient" Then |
| 59 | + conn = New SqlConnection(typeAndDsn(1)) |
| 60 | + ElseIf typeAndDsn(0) = "MySql.Data.MySqlClient" Then |
| 61 | + conn = New MySqlConnection(typeAndDsn(1)) |
| 62 | + End If |
| 63 | + Connection.register(processAndThreadKey, connectionIndex, conn) |
| 64 | + Try |
| 65 | + conn.Open() |
| 66 | + Connection.addErrorHandler(conn) |
| 67 | + Catch ex As Exception |
| 68 | + Debug.Log(ex) |
| 69 | + End Try |
| 70 | + End If |
| 71 | + Return conn |
| 72 | + End Function |
| 73 | + ''' <summary> |
| 74 | + ''' Get and open connection by config name. |
| 75 | + ''' </summary> |
| 76 | + ''' <param name="connectionName">Config connection name.</param> |
| 77 | + ''' <returns></returns> |
| 78 | + Public Shared Function [Get](connectionName As String) As DbConnection |
| 79 | + Dim connIndex As Int32 |
| 80 | + If Connection.namesAndIndexes.ContainsKey(connectionName) Then |
| 81 | + connIndex = Connection.namesAndIndexes(connectionName) |
| 82 | + Else |
| 83 | + Throw New Exception($"Connection settings under name doesn't exist: {connectionName}.") |
| 84 | + End If |
| 85 | + Return Connection.Get(connIndex) |
| 86 | + End Function |
| 87 | + ''' <summary> |
| 88 | + ''' Close and drop all connections for current process and thread. |
| 89 | + ''' Call this method always at thread end. |
| 90 | + ''' </summary> |
| 91 | + Public Shared Sub Close() |
| 92 | + Dim processAndThreadKey = Connection.getProcessAndThreadKey() |
| 93 | + Dim conn As DbConnection |
| 94 | + If Connection.connections.ContainsKey(processAndThreadKey) Then |
| 95 | + For Each item In Connection.connections(processAndThreadKey) |
| 96 | + conn = item.Value |
| 97 | + If conn.State = ConnectionState.Open Then |
| 98 | + conn.Close() |
| 99 | + conn.Dispose() |
| 100 | + End If |
| 101 | + Next |
| 102 | + SyncLock Connection.threadLock |
| 103 | + Connection.connections.Remove(processAndThreadKey) |
| 104 | + End SyncLock |
| 105 | + End If |
| 106 | + End Sub |
| 107 | + ''' <summary> |
| 108 | + ''' Close and drop connection by config index for current process and thread. |
| 109 | + ''' Call this method always after you have loaded all from any secondary database. |
| 110 | + ''' </summary> |
| 111 | + ''' <param name="connectionIndex">Config connection index.</param> |
| 112 | + Public Shared Sub Close(connectionIndex As Int32) |
| 113 | + Connection.Close(connectionIndex, Process.GetCurrentProcess().Id, Thread.CurrentThread.ManagedThreadId) |
| 114 | + End Sub |
| 115 | + ''' <summary> |
| 116 | + ''' Close and drop connection by config name for current process And thread. |
| 117 | + ''' Call this method always after you have loaded all from any secondary database. |
| 118 | + ''' </summary> |
| 119 | + ''' <param name="connectionName">Config connection name.</param> |
| 120 | + Public Shared Sub Close(connectionName As String) |
| 121 | + If Connection.namesAndIndexes.ContainsKey(connectionName) Then |
| 122 | + Connection.Close(Connection.namesAndIndexes(connectionName), Process.GetCurrentProcess().Id, Thread.CurrentThread.ManagedThreadId) |
| 123 | + End If |
| 124 | + End Sub |
| 125 | + ''' <summary> |
| 126 | + ''' Close and drop connection by config index for specificly called process and thread. |
| 127 | + ''' Call this method only if you know what you are doing:-) |
| 128 | + ''' </summary> |
| 129 | + ''' <param name="connectionIndex">Config connection index.</param> |
| 130 | + ''' <param name="processId">Specific process id.</param> |
| 131 | + ''' <param name="threadId">Specific thread id.</param> |
| 132 | + Public Shared Sub Close(connectionIndex As Int32, processId As Int32, threadId As Int32) |
| 133 | + Dim processAndThreadKey = $"{processId}_{threadId}" |
| 134 | + Dim conns As Dictionary(Of Int16, DbConnection) |
| 135 | + Dim conn As DbConnection |
| 136 | + If Connection.connections.ContainsKey(processAndThreadKey) Then |
| 137 | + conns = Connection.connections(processAndThreadKey) |
| 138 | + conn = conns(connectionIndex) |
| 139 | + If conn.State = ConnectionState.Open Then |
| 140 | + conn.Close() |
| 141 | + conn.Dispose() |
| 142 | + End If |
| 143 | + SyncLock Connection.threadLock |
| 144 | + conns.Remove(connectionIndex) |
| 145 | + If conns.Count = 0 Then |
| 146 | + conns.Remove(processAndThreadKey) |
| 147 | + End If |
| 148 | + End SyncLock |
| 149 | + End If |
| 150 | + End Sub |
| 151 | + ''' <summary> |
| 152 | + ''' Close and drop connections for specificly called process. |
| 153 | + ''' </summary> |
| 154 | + Public Shared Sub CloseAllInProcess(processId As Int32) |
| 155 | + Dim keyBegin As String = processId.ToString() + "_" |
| 156 | + Dim key As String |
| 157 | + Dim conns As Dictionary(Of Int16, DbConnection) |
| 158 | + SyncLock Connection.threadLock |
| 159 | + For i As Int32 = Connection.connections.Keys.Count - 1 To 0 Step -1 |
| 160 | + If Connection.connections.Keys.Contains(i) Then |
| 161 | + key = Connection.connections.Keys(i) |
| 162 | + Else |
| 163 | + Continue For |
| 164 | + End If |
| 165 | + If key.IndexOf(keyBegin) = 0 Then |
| 166 | + conns = Connection.connections(key) |
| 167 | + For Each item In conns |
| 168 | + If item.Value.State = ConnectionState.Open Then |
| 169 | + item.Value.Close() |
| 170 | + item.Value.Dispose() |
| 171 | + End If |
| 172 | + Next |
| 173 | + Connection.connections.Remove(key) |
| 174 | + End If |
| 175 | + Next |
| 176 | + End SyncLock |
| 177 | + End Sub |
| 178 | + ''' <summary> |
| 179 | + ''' Create and begin transaction on first config connection. |
| 180 | + ''' </summary> |
| 181 | + ''' <param name="transactionName">Transaction name.</param> |
| 182 | + ''' <param name="isolationLevel">Transaction isolation level.</param> |
| 183 | + ''' <returns>New transaction.</returns> |
| 184 | + Public Shared Function BeginTransaction(Optional transactionName As String = "", Optional isolationLevel As IsolationLevel = IsolationLevel.Unspecified) As DbTransaction |
| 185 | + Dim defaultConnectionIndex As Int16 = 0 |
| 186 | + Return Connection.BeginTransaction(defaultConnectionIndex, transactionName, isolationLevel) |
| 187 | + End Function |
| 188 | + ''' <summary> |
| 189 | + ''' Create and begin transaction on specified connection config index. |
| 190 | + ''' </summary> |
| 191 | + ''' <param name="connectionIndex">Config connection index.</param> |
| 192 | + ''' <param name="transactionName">Transaction name.</param> |
| 193 | + ''' <param name="isolationLevel">Transaction isolation level.</param> |
| 194 | + ''' <returns>New transaction.</returns> |
| 195 | + Public Shared Function BeginTransaction(connectionIndex As Int16, Optional transactionName As String = "", Optional isolationLevel As IsolationLevel = IsolationLevel.Unspecified) As DbTransaction |
| 196 | + Dim conn As DbConnection = Connection.Get(connectionIndex) |
| 197 | + Dim connType As Type = conn.GetType() |
| 198 | + If connType.IsAssignableFrom(GetType(SqlConnection)) Then |
| 199 | + Dim msConn As SqlConnection = DirectCast(conn, SqlConnection) |
| 200 | + Return msConn.BeginTransaction(isolationLevel, transactionName) |
| 201 | + ElseIf connType.IsAssignableFrom(GetType(MySqlConnection)) Then |
| 202 | + Dim myConn As MySqlConnection = DirectCast(conn, MySqlConnection) |
| 203 | + Return myConn.BeginTransaction(isolationLevel) |
| 204 | + End If |
| 205 | + Return Nothing |
| 206 | + End Function |
| 207 | + ''' <summary> |
| 208 | + ''' Create and begin transaction on specified connection config name. |
| 209 | + ''' </summary> |
| 210 | + ''' <param name="connectionName">Config connection name.</param> |
| 211 | + ''' <param name="transactionName">Transaction name.</param> |
| 212 | + ''' <param name="isolationLevel">Transaction isolation level.</param> |
| 213 | + ''' <returns>New transaction.</returns> |
| 214 | + Public Shared Function BeginTransaction(connectionName As String, Optional transactionName As String = "", Optional isolationLevel As IsolationLevel = IsolationLevel.Unspecified) As DbTransaction |
| 215 | + Dim connectionIndex As Int16 |
| 216 | + If Connection.namesAndIndexes.ContainsKey(connectionName) Then |
| 217 | + connectionIndex = Connection.namesAndIndexes(connectionName) |
| 218 | + Else |
| 219 | + Throw New Exception($"Connection settings under name doesn't exist: {connectionName}.") |
| 220 | + End If |
| 221 | + Return Connection.BeginTransaction(connectionIndex, transactionName, isolationLevel) |
| 222 | + End Function |
| 223 | + ''' <summary> |
| 224 | + ''' Add InfoMessage handler for opened connection to to log SQL errors. |
| 225 | + ''' </summary> |
| 226 | + ''' <param name="conn"></param> |
| 227 | + Protected Shared Sub addErrorHandler(conn As DbConnection) |
| 228 | + Dim connType As Type = conn.GetType() |
| 229 | + If connType.IsAssignableFrom(GetType(SqlConnection)) Then |
| 230 | + Dim msConn As SqlConnection = DirectCast(conn, SqlConnection) |
| 231 | + AddHandler msConn.InfoMessage, AddressOf Connection.errorHandler |
| 232 | + ElseIf connType.IsAssignableFrom(GetType(MySqlConnection)) Then |
| 233 | + Dim myConn As MySqlConnection = DirectCast(conn, MySqlConnection) |
| 234 | + AddHandler myConn.InfoMessage, AddressOf Connection.errorHandler |
| 235 | + End If |
| 236 | + End Sub |
| 237 | + Protected Shared Sub errorHandler(sender As Object, sqlErrorArgs As EventArgs) |
| 238 | + Dim fi As FieldInfo = sqlErrorArgs.GetType().GetField("exception", BindingFlags.NonPublic) |
| 239 | + Dim exception = fi.GetValue(sqlErrorArgs) |
| 240 | + If Debug.Enabled() Then |
| 241 | + Debug.Dump(exception) |
| 242 | + Else |
| 243 | + Debug.Log(exception) |
| 244 | + End If |
| 245 | + End Sub |
| 246 | + |
| 247 | + Protected Shared Sub register(processAndThreadKey As String, connectionIndex As Int32, conn As DbConnection) |
| 248 | + Dim conns As Dictionary(Of Int16, DbConnection) |
| 249 | + SyncLock Connection.threadLock |
| 250 | + If Connection.connections.ContainsKey(processAndThreadKey) Then |
| 251 | + conns = Connection.connections(processAndThreadKey) |
| 252 | + conns(connectionIndex) = conn |
| 253 | + Else |
| 254 | + Connection.connections.Add(processAndThreadKey, New Dictionary(Of Short, DbConnection) From { |
| 255 | + {connectionIndex, conn} |
| 256 | + }) |
| 257 | + End If |
| 258 | + End SyncLock |
| 259 | + End Sub |
| 260 | + |
| 261 | + Protected Shared Function getProcessAndThreadKey() As String |
| 262 | + Return $"{Process.GetCurrentProcess().Id}_{Thread.CurrentThread.ManagedThreadId}" |
| 263 | + End Function |
| 264 | + |
| 265 | +End Class |
0 commit comments