drupal hit counter
Jerry Huang | SqlCommand to Sql statement

Jerry Huang apps and developing apps

SqlCommand to Sql statement

18. July 2010 22:31 by Jerry in Old blog posts

Recently I have a requirement that log down the message when an error occurs on the server. If the error was caused by execution of SQL statement, log that as well.

It came to a problem that when the SQL is carried by a SqlCommand object, there is no any built-in function to get back the SQL statement in a string format. I wrote a small VB module to do the job.

The primary idea is going through the CommandText, record every place (replace index) that need to replace the parameter name (starts with “@"), and then replace them.

 

The code is written in VB.NET, but it should not be difficult to convert to C# by using http://www.developerfusion.com/tools/convert/vb-to-csharp/

Usage:

smply call SqlCommandToSql and pass in a SqlCommand object.

Dim cmd As New SqlClient.SqlCommand()
cmd.CommandText = "update abcd set a=@a,b=@ab, c=@abc where cd=@a"
cmd.Parameters.Add("@a", SqlDbType.VarChar).Value = "a@aa"
cmd.Parameters.Add("@ab", SqlDbType.Bit).Value = False
cmd.Parameters.Add("@abc", SqlDbType.DateTime).Value = Now
MsgBox(SqlCommandToSql(cmd)) 

 

source code:

Imports System.Data.SqlClient

Friend Module Module1
Private Class ReplaceIndex
Implements IComparable

Public Index As Integer
Public ParaName As String
Public Sub New(ByVal idx As Integer, ByVal para As String)
Index = idx
ParaName = para
End Sub

Public Function CompareTo(ByVal obj As Object) As Integer Implements System.IComparable.CompareTo
Dim r As ReplaceIndex = DirectCast(obj, ReplaceIndex)
Return Me.Index - r.Index
End Function
End Class

Private Function GetParaValue(ByVal para As SqlParameter) As String
Dim value As String = ""
If para.Value Is Nothing Then
'it should throw an error if an parameter had not set value
'but here just make it through
value = "Nothing"
End If
If para.Value Is DBNull.Value Then
value = "NULL"
End If
value = para.Value.ToString()
'special handle for some types
Select Case para.SqlDbType
Case SqlDbType.VarChar, SqlDbType.Char, SqlDbType.Text, SqlDbType.NChar, SqlDbType.NText, SqlDbType.NVarChar
value = "'" & value.Replace("'", "''") & "'"
Case SqlDbType.Date, SqlDbType.DateTime, SqlDbType.DateTime2, SqlDbType.Time
value = "'" & value & "'"
Case SqlDbType.Binary, SqlDbType.Image, SqlDbType.VarBinary, SqlDbType.Timestamp
value = "0x" & BitConverter.ToString(TryCast(para.Value, Byte())).Replace("-", "")
Case SqlDbType.Bit
If TypeOf para.Value Is Boolean Then
value = IIf(para.Value, "1", "0")
End If
End Select
Return value
End Function

Private Sub Replace(ByRef source As String, ByVal startIdx As Integer, ByVal paraName As String, ByVal paraType As SqlDbType, ByVal value As String)
Dim len As Integer = paraName.Length
source = source.Remove(startIdx, len)
source = source.Insert(startIdx, value)

End Sub




Public Function SqlCommandToSql(ByVal cmd As SqlCommand) As String
'loop through the parameters
'record the start index where need to replace every para name
'loop the index array, replace para name one by one
'when replace one, need to adjust the rest indexs, since the string length might be changed
Dim index As New Hashtable()

Dim sql As String = cmd.CommandText

For Each p As SqlParameter In cmd.Parameters
FindIndex(index, sql, 0, p)
'index.Add(p, idx)
Next

Dim keyCollection As ICollection = index.Keys
Dim keys As ReplaceIndex() = New ReplaceIndex(keyCollection.Count - 1) {}
keyCollection.CopyTo(keys, 0)
'sort the index first
Array.Sort(keys)

Dim offset As Integer = 0
'loop through each replace point(index)
For i As Integer = 0 To keys.Length - 1
Dim r As ReplaceIndex = DirectCast(keys(i), ReplaceIndex)
Dim p As SqlParameter = DirectCast(index(r), SqlParameter)
Dim value As String = GetParaValue(p)
r.Index += offset
Replace(sql, r.Index, p.ParameterName, p.SqlDbType, value)
'calc the total offset
offset += value.Length - p.ParameterName.Length
Next

Return sql
End Function

Private Sub FindIndex(ByVal ht As Hashtable, ByVal source As String, ByVal startIdx As Integer, ByVal para As SqlParameter)
Dim idx As Integer = source.IndexOf(para.ParameterName, startIdx)
If idx < 0 Then
'no more match is found
Exit Sub
End If
Dim idx2 As Integer = source.LastIndexOf(para.ParameterName)
If idx = idx2 Then
'only one match in the remaining string
idx2 += para.ParameterName.Length
If idx2 >= source.Length Then
Dim r As New ReplaceIndex(idx, para.ParameterName)
ht.Add(r, para)
Else
Dim nextChar As String = source.Substring(idx2, 1)
'if the next char is special charater, this is the match we are looking for
If " ~!@#$%^&*()+-={}[]\|;:,./?'""".Contains(nextChar) Then
Dim r As New ReplaceIndex(idx, para.ParameterName)
ht.Add(r, para)

End If
End If

Exit Sub
End If

'more than once match the parameter name
'e.g. @a will match @a, @ab and @abc, etc.
'store each start index
Dim index As New List(Of Integer)
index.Add(idx)
'index.Add(idx2)
idx = source.IndexOf(para.ParameterName, idx + para.ParameterName.Length)
While idx <> idx2
index.Add(idx)
idx = source.IndexOf(para.ParameterName, idx + para.ParameterName.Length)
End While
'find the actual match index
For Each i As Integer In index
idx2 = i + para.ParameterName.Length
startIdx = idx2
If idx2 = source.Length Then
'this match is the last one (reach the end of source string)
Dim r As New ReplaceIndex(i, para.ParameterName)
ht.Add(r, para)
Exit Sub
Else
Dim nextChar As String = source.Substring(idx2, 1)
'if the next char is special charater, this is the match we are looking for
If " ~!@#$%^&*()+-={}[]\|;:,./?'""".Contains(nextChar) Then
Dim r As New ReplaceIndex(i, para.ParameterName)
ht.Add(r, para)

FindIndex(ht, source, startIdx, para)
Exit Sub
End If
End If
Next
FindIndex(ht, source, startIdx, para)
End Sub
End Module