Получить сгенерированный оператор SQL из объекта SqlCommand? - PullRequest
167 голосов
/ 05 ноября 2008

У меня есть следующий код:

Using cmd As SqlCommand = Connection.CreateCommand
    cmd.CommandText = "UPDATE someTable SET Value = @Value"
    cmd.CommandText &= " WHERE Id = @Id"
    cmd.Parameters.AddWithValue("@Id", 1234)
    cmd.Parameters.AddWithValue("@Value", "myValue")
    cmd.ExecuteNonQuery
End Using

Интересно, есть ли способ получить окончательный SQL-оператор в виде строки, который должен выглядеть следующим образом:

UPDATE someTable SET Value = "myValue" WHERE Id = 1234

Если кто-то задается вопросом, зачем мне это делать:

  • для записи (сбой) операторов
  • за возможность скопировать и вставить его в Enterprise Manager для тестирования

Ответы [ 20 ]

1 голос
/ 21 февраля 2014

У меня был точно такой же вопрос, и после прочтения этих ответов по ошибке решил, что невозможно получить точный результирующий запрос. Я был неправ.

Решение: Откройте Activity Monitor в SQL Server Management Studio, сузьте раздел процессов до имени пользователя для входа, базы данных или имени приложения, которое ваше приложение использует в строке подключения. При звонке в БД обновляется Activity Monitor. Когда вы увидите процесс, щелкните по нему правой кнопкой мыши и View Details.

Обратите внимание, что это не может быть приемлемым вариантом для занятых БД. Но вы должны быть в состоянии значительно сузить результат, используя эти шаги.

1 голос
/ 16 января 2014

Если проверить только то, как параметр отформатирован в результате запроса, большинство СУБД позволяет запрашивать литералы из ничего. Таким образом:

Using cmd As SqlCommand = Connection.CreateCommand
    cmd.CommandText = "SELECT @Value"
    cmd.Parameters.AddWithValue("@Value", "myValue")
    Return cmd.ExecuteScalar
End Using

Таким образом, вы можете увидеть, удвоены ли кавычки и т. Д.

1 голос
/ 23 мая 2019

Поздний ответ, я знаю, но я тоже хотел это, чтобы я мог регистрировать SQL. Следующее короткое и соответствует моим потребностям.

Следующее создает SQL, который вы можете копировать / вставлять в SSMS (он заменяет параметры на значения правильно) Вы можете добавить больше типов, но это соответствует всем, что я использую в этом случае.

    private static void LogSQL(SqlCommand cmd)
        {
            string query = cmd.CommandText;

            foreach (SqlParameter prm in cmd.Parameters)
            {
                switch (prm.SqlDbType)
                {
                    case SqlDbType.Bit:
                        int boolToInt = (bool)prm.Value ? 1 : 0;
                        query = query.Replace(prm.ParameterName, string.Format("{0}", (bool)prm.Value ? 1 : 0));
                        break;
                    case SqlDbType.Int:
                        query = query.Replace(prm.ParameterName, string.Format("{0}", prm.Value));
                        break;
                    case SqlDbType.VarChar:
                        query = query.Replace(prm.ParameterName, string.Format("'{0}'", prm.Value));
                        break;
                    default:
                        query = query.Replace(prm.ParameterName, string.Format("'{0}'", prm.Value));
                        break;
                }
            }

            // the following is my how I write to my log - your use will vary
            logger.Debug("{0}", query);

            return;
        }

Теперь я могу записать SQL перед тем, как его выполнить:

LogSQL(queryCmd)
queryCmd.ExecuteNonQuery()
1 голос
/ 15 июня 2016

Мое решение:

public static class DbHelper
{
    public static string ToString(this DbParameterCollection parameters, string sqlQuery)
    {
        return parameters.Cast<DbParameter>().Aggregate(sqlQuery, (current, p) => current.Replace(p.ParameterName, p.Value.ToString()));
    }
}
1 голос
/ 16 марта 2015

Модифицированная версия Ответ Кона , поскольку он только частично работает с аналогичными именованными параметрами. Недостаток использования функции String Replace. Кроме этого, я полностью доверяю ему.

    private string GetActualQuery(SqlCommand sqlcmd)
    {
        string query = sqlcmd.CommandText;
        string parameters = "";
        string[] strArray = System.Text.RegularExpressions.Regex.Split(query, " VALUES ");

        //Reconstructs the second half of the SQL Command
        parameters = "(";

        int count = 0;
        foreach (SqlParameter p in sqlcmd.Parameters)
        {
            if (count == (sqlcmd.Parameters.Count - 1))
            {
                parameters += p.Value.ToString();
            }
            else
            {
                parameters += p.Value.ToString() + ", ";
            }
            count++;
        }

        parameters += ")";

        //Returns the string recombined.
        return strArray[0] + " VALUES " + parameters;
    }
0 голосов
/ 19 сентября 2018

необходимо также, чтобы охватить не сохраненные процедуры, поэтому я добавил библиотеку CommandAsSql (см. Комментарии к ответу @ Flapper выше) с помощью этой логики:

    private static void CommandAsSql_Text(this SqlCommand command, System.Text.StringBuilder sql)
    {
        string query = command.CommandText;

        foreach (SqlParameter p in command.Parameters)
            query = Regex.Replace(query, "\\B" + p.ParameterName + "\\b", p.ParameterValueForSQL()); //the first one is \B, the 2nd one is \b, since ParameterName starts with @ which is a non-word character in RegEx (see https://stackoverflow.com/a/2544661)

        sql.AppendLine(query);
    }

запрос на получение по адресу: https://github.com/jphellemons/CommandAsSql/pull/3/commits/527d696dc6055c5bcf858b9700b83dc863f04896

Идея Regex была основана на комментариях @ stambikk и EvZ выше и в разделе "Обновление:" https://stackoverflow.com/a/2544661/903783, в котором упоминается "утверждение с отрицательным подтверждением". Использование \ B вместо \ b для обнаружения границы слова в начале регулярного выражения объясняется тем, что p.parameterName всегда будет начинаться с символа "@", который не является символом слова.

обратите внимание, что ParameterValueForSQL () - это метод расширения, определенный в библиотеке CommandAsSql для обработки таких проблем, как значения параметров строки в одинарных кавычках и т. Д.

0 голосов
/ 06 апреля 2011

Это решение работает для меня прямо сейчас. Может быть, это кому-то полезно. Пожалуйста, извините за все избыточность.

    Public Shared Function SqlString(ByVal cmd As SqlCommand) As String
    Dim sbRetVal As New System.Text.StringBuilder()
    For Each item As SqlParameter In cmd.Parameters
        Select Case item.DbType
            Case DbType.String
                sbRetVal.AppendFormat("DECLARE {0} AS VARCHAR(255)", item.ParameterName)
                sbRetVal.AppendLine()
                sbRetVal.AppendFormat("SET {0} = '{1}'", item.ParameterName, item.Value)
                sbRetVal.AppendLine()

            Case DbType.DateTime
                sbRetVal.AppendFormat("DECLARE {0} AS DATETIME", item.ParameterName)
                sbRetVal.AppendLine()
                sbRetVal.AppendFormat("SET {0} = '{1}'", item.ParameterName, item.Value)
                sbRetVal.AppendLine()

            Case DbType.Guid
                sbRetVal.AppendFormat("DECLARE {0} AS UNIQUEIDENTIFIER", item.ParameterName)
                sbRetVal.AppendLine()
                sbRetVal.AppendFormat("SET {0} = '{1}'", item.ParameterName, item.Value)
                sbRetVal.AppendLine()

            Case DbType.Int32
                sbRetVal.AppendFormat("DECLARE {0} AS int", item.ParameterName)
                sbRetVal.AppendLine()
                sbRetVal.AppendFormat("SET {0} = {1}", item.ParameterName, item.Value)
                sbRetVal.AppendLine()

            Case Else
                Stop

        End Select
    Next

    sbRetVal.AppendLine("")
    sbRetVal.AppendLine(cmd.CommandText)

    Return sbRetVal.ToString()
End Function
0 голосов
/ 22 февраля 2017

Один лайнер:

string.Join(",", from SqlParameter p in cmd.Parameters select p.ToString()) 
0 голосов
/ 06 декабря 2016

запросы команды sql будут выполняться с помощью exec sp_executesql, поэтому вот еще один способ получить инструкцию в виде строки (метод расширения SqlCommand):

public static string ToSqlStatement(this SqlCommand cmd)
{
    return $@"EXECUTE sp_executesql N'{cmd.CommandText.Replace("'", "''")}'{cmd.Parameters.ToSqlParameters()}";
}

private static string ToSqlParameters(this SqlParameterCollection col)
{
    if (col.Count == 0)
        return string.Empty;
    var parameters = new List<string>();
    var parameterValues = new List<string>();
    foreach (SqlParameter param in col)
    {
        parameters.Add($"{param.ParameterName}{param.ToSqlParameterType()}");
        parameterValues.Add($"{param.ParameterName} = {param.ToSqlParameterValue()}");
    }
    return $",N\'{string.Join(",", parameters)}\',{string.Join(",", parameterValues)}";
}

private static object ToSqlParameterType(this SqlParameter param)
{
    var paramDbType = param.SqlDbType.ToString().ToLower();
    if (param.Precision != 0 && param.Scale != 0)
        return $"{paramDbType}({param.Precision},{param.Scale})";
    if (param.Precision != 0)
        return $"{paramDbType}({param.Precision})";
    switch (param.SqlDbType)
    {
        case SqlDbType.VarChar:
        case SqlDbType.NVarChar:
            string s = param.SqlValue?.ToString() ?? string.Empty;
            return paramDbType + (s.Length > 0 ? $"({s.Length})" : string.Empty);
        default:
            return paramDbType;
    }
}

private static string ToSqlParameterValue(this SqlParameter param)
{
    switch (param.SqlDbType)
    {
        case SqlDbType.Char:
        case SqlDbType.Date:
        case SqlDbType.DateTime:
        case SqlDbType.DateTime2:
        case SqlDbType.DateTimeOffset:
        case SqlDbType.NChar:
        case SqlDbType.NText:
        case SqlDbType.NVarChar:
        case SqlDbType.Text:
        case SqlDbType.Time:
        case SqlDbType.VarChar:
        case SqlDbType.Xml:
            return $"\'{param.SqlValue.ToString().Replace("'", "''")}\'";
        case SqlDbType.Bit:
            return param.SqlValue.ToBooleanOrDefault() ? "1" : "0";
        default:
            return param.SqlValue.ToString().Replace("'", "''");
    }
}

public static bool ToBooleanOrDefault(this object o, bool defaultValue = false)
{
    if (o == null)
        return defaultValue;
    string value = o.ToString().ToLower();
    switch (value)
    {
        case "yes":
        case "true":
        case "ok":
        case "y":
            return true;
        case "no":
        case "false":
        case "n":
            return false;
        default:
            bool b;
            if (bool.TryParse(o.ToString(), out b))
                return b;
            break;
    }
    return defaultValue;
}
0 голосов
/ 15 июля 2015

Как упоминали @pkExec и @Alok, использование Replace не работает в 100% случаев. Это решение, которое я использовал в нашем DAL, которое использует RegExp только для «соответствия целому слову» и правильного форматирования типов данных. Таким образом, сгенерированный SQL может быть протестирован непосредственно в MySQL Workbench (или SQLSMS и т. Д.):)

(Заменить функцию MySQLHelper.EscapeString () в соответствии с используемой СУБД.)

Dim query As String = cmd.CommandText
query = query.Replace("SET", "SET" & vbNewLine)
query = query.Replace("WHERE", vbNewLine & "WHERE")
query = query.Replace("GROUP BY", vbNewLine & "GROUP BY")
query = query.Replace("ORDER BY", vbNewLine & "ORDER BY")
query = query.Replace("INNER JOIN", vbNewLine & "INNER JOIN")
query = query.Replace("LEFT JOIN", vbNewLine & "LEFT JOIN")
query = query.Replace("RIGHT JOIN", vbNewLine & "RIGHT JOIN")
If query.Contains("UNION ALL") Then
    query = query.Replace("UNION ALL", vbNewLine & "UNION ALL" & vbNewLine)
ElseIf query.Contains("UNION DISTINCT") Then
    query = query.Replace("UNION DISTINCT", vbNewLine & "UNION DISTINCT" & vbNewLine)
Else
    query = query.Replace("UNION", vbNewLine & "UNION" & vbNewLine)
End If

For Each par In cmd.Parameters
    If par.Value Is Nothing OrElse IsDBNull(par.Value) Then
        query = RegularExpressions.Regex.Replace(query, par.ParameterName & "\b", "NULL")
    ElseIf TypeOf par.Value Is Date Then
        query = RegularExpressions.Regex.Replace(query, par.ParameterName & "\b", "'" & Format(par.Value, "yyyy-MM-dd HH:mm:ss") & "'")
    ElseIf TypeOf par.Value Is TimeSpan Then
        query = RegularExpressions.Regex.Replace(query, par.ParameterName & "\b", "'" & par.Value.ToString & "'")
    ElseIf TypeOf par.Value Is Double Or TypeOf par.Value Is Decimal Or TypeOf par.Value Is Single Then
        query = RegularExpressions.Regex.Replace(query, par.ParameterName & "\b", Replace(par.Value.ToString, ",", "."))
    ElseIf TypeOf par.Value Is Integer Or TypeOf par.Value Is UInteger Or TypeOf par.Value Is Long Or TypeOf par.Value Is ULong Then
        query = RegularExpressions.Regex.Replace(query, par.ParameterName & "\b", par.Value.ToString)
    Else
        query = RegularExpressions.Regex.Replace(query, par.ParameterName & "\b", "'" & MySqlHelper.EscapeString(CStr(par.Value)) & "'")
    End If
Next

Пример:

SELECT * FROM order WHERE order_status = @order_status AND order_date = @order_date

Будет сгенерировано:

SELECT * FROM order WHERE order_status = 'C' AND order_date = '2015-01-01 00:00:00'
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...