Получить сгенерированный оператор SQL из объекта SqlCommand?


186

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

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 для тестирования

1
Почему вы отметили ответ stackoverflow.com/a/265261/206730, если не различаете разные типы данных, инъекцию Sql, имена параметров одинаковые (проблема замены) ...?
Kiquenet

@Kiquenet Я мог поклясться, что попробовал, но это не позволило мне. Теперь это работает. Спасибо тебе за это.
Пустышка

Если вы хотите точно сгенерировать SQL, который будет запущен, посмотрите на TdsParser.TdsExecuteRPC ( github.com/Microsoft/referencesource/blob/master/System.Data/… ) и немного пугайтесь .
Рори

Ответы:


110

Хотя это и не идеально, вот кое-что, что я разработал для TSQL - его можно легко настроить для других вариантов ... Если ничего другого, это даст вам отправную точку для ваших собственных улучшений :)

Это хорошо выполняет работу с типами данных и параметрами вывода и т. Д., Аналогично использованию «выполнить хранимую процедуру» в SSMS. В основном мы использовали SP, поэтому команда text не учитывает параметры и т. Д.

    public static String ParameterValueForSQL(this SqlParameter sp)
    {
        String retval = "";

        switch (sp.SqlDbType)
        {
            case SqlDbType.Char:
            case SqlDbType.NChar:
            case SqlDbType.NText:
            case SqlDbType.NVarChar:
            case SqlDbType.Text:
            case SqlDbType.Time:
            case SqlDbType.VarChar:
            case SqlDbType.Xml:
            case SqlDbType.Date:
            case SqlDbType.DateTime:
            case SqlDbType.DateTime2:
            case SqlDbType.DateTimeOffset:
                retval = "'" + sp.Value.ToString().Replace("'", "''") + "'";
                break;

            case SqlDbType.Bit:
                retval = (sp.Value.ToBooleanOrDefault(false)) ? "1" : "0";
                break;

            default:
                retval = sp.Value.ToString().Replace("'", "''");
                break;
        }

        return retval;
    }

    public static String CommandAsSql(this SqlCommand sc)
    {
        StringBuilder sql = new StringBuilder();
        Boolean FirstParam = true;

        sql.AppendLine("use " + sc.Connection.Database + ";");
        switch (sc.CommandType)
        {
            case CommandType.StoredProcedure:
                sql.AppendLine("declare @return_value int;");

                foreach (SqlParameter sp in sc.Parameters)
                {
                    if ((sp.Direction == ParameterDirection.InputOutput) || (sp.Direction == ParameterDirection.Output))
                    {
                        sql.Append("declare " + sp.ParameterName + "\t" + sp.SqlDbType.ToString() + "\t= ");

                        sql.AppendLine(((sp.Direction == ParameterDirection.Output) ? "null" : sp.ParameterValueForSQL()) + ";");

                    }
                }

                sql.AppendLine("exec [" + sc.CommandText + "]");

                foreach (SqlParameter sp in sc.Parameters)
                {
                    if (sp.Direction != ParameterDirection.ReturnValue)
                    {
                        sql.Append((FirstParam) ? "\t" : "\t, ");

                        if (FirstParam) FirstParam = false;

                        if (sp.Direction == ParameterDirection.Input)
                            sql.AppendLine(sp.ParameterName + " = " + sp.ParameterValueForSQL());
                        else

                            sql.AppendLine(sp.ParameterName + " = " + sp.ParameterName + " output");
                    }
                }
                sql.AppendLine(";");

                sql.AppendLine("select 'Return Value' = convert(varchar, @return_value);");

                foreach (SqlParameter sp in sc.Parameters)
                {
                    if ((sp.Direction == ParameterDirection.InputOutput) || (sp.Direction == ParameterDirection.Output))
                    {
                        sql.AppendLine("select '" + sp.ParameterName + "' = convert(varchar, " + sp.ParameterName + ");");
                    }
                }
                break;
            case CommandType.Text:
                sql.AppendLine(sc.CommandText);
                break;
        }

        return sql.ToString();
    }

это генерирует вывод по этим направлениям ...

use dbMyDatabase;
declare @return_value int;
declare @OutTotalRows   BigInt  = null;
exec [spMyStoredProc]
    @InEmployeeID = 1000686
    , @InPageSize = 20
    , @InPage = 1
    , @OutTotalRows = @OutTotalRows output
;
select 'Return Value' = convert(varchar, @return_value);
select '@OutTotalRows' = convert(varchar, @OutTotalRows);

7
Хорошая работа на самом деле пытается решить проблему здесь, проголосовал за усилия в одиночку.
Адам Толли

3
Каким будет ваш метод ToBooleanOrDefault (false)?
Benoittr

6
@Benoittr, вы можете увидеть реализацию ToBooleanOrDefaultздесь: Вопрос № 3244850
Александр

@flapper, что из поля двоичных объектов или байтового массива
Смит

1
Сделаны некоторые незначительные корректировки и добавлены параметры таблицы значений. Это все на GitHub и пакете Nuget .Net Standard 2.0 github.com/jphellemons/CommandAsSql Спасибо, Флаппер! Могу ли я добавить вас в качестве соавтора?
JP

128

Боюсь, что для ведения журналов нет лучшего способа сделать это, кроме как создать строку самостоятельно:

string query = cmd.CommandText;

foreach (SqlParameter p in cmd.Parameters)
{
    query = query.Replace(p.ParameterName, p.Value.ToString());
}

Если я сделаю это, мне придется различать разные типы данных. Тогда я мог бы пропустить параметризованный запрос все вместе и выполнить это.
пустышка

2
пустышка: не совсем. если вы выполняете подготовленный оператор, вы рискуете получить SQL-инъекцию. +1 за ответ.
Солнечный Миленов

11
Здесь есть ошибки. Если у меня есть «Param» и «differentParam» в качестве параметров, то он делает DifferentParam бесполезным, поскольку он заменяет его на «ValueParam». при условии Param = Value.
Alok

5
Этот вопрос не касается методов защитного кодирования, поэтому проверка нулевых ссылок не является частью ответа. Тот факт, что это должно быть реализовано, подразумевается, поэтому я не рассматриваю это как конструктивный комментарий.
Кон

2
несколько лучший подход для устранения проблемы с похожими именами параметров, указанными @Alok, может быть использован query = Regex.Replace(query, @"\b" + p.ParameterName + @"\b", p.Value.ToString());для замены параметров в строке. Это заменит «целое слово». Возможно, это не универсальное решение, поскольку \ b помечает позицию между символом слова и символом, отличным от слова, поэтому в случае, если имена ваших параметров начинаются с @, вы должны использовать p.ParameterName + @"\b"для замены параметра в строке запроса.
Stambikk

47

Вы не можете, потому что он не генерирует SQL.

Параметризованный запрос (входящий в систему CommandText) отправляется на SQL Server как эквивалент подготовленного оператора. При выполнении команды параметры и текст запроса обрабатываются отдельно. В любой момент времени полная строка SQL не генерируется.

Вы можете использовать SQL Profiler, чтобы заглянуть за кулисы.


6
SQL генерируется - посмотрите в Profiler - это текст, который я хотел бы иметь для ведения журналов
kpkpkp

кроме SQL Profiler (который считается устаревшим для более нового SQL Server, если я правильно понял некоторые комментарии MS), можно также использовать Activity Monitor в соответствии с другим ответом здесь
Джордж Бирбилис

27

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

Пример:

SqlCommand cmd = new SqlCommand("GetEntity", con);
cmd.Parameters.AddWithValue("@foobar", 1);
cmd.Parameters.Add(new SqlParameter(){
    ParameterName = "@outParam",
    Direction = ParameterDirection.Output,
    SqlDbType = System.Data.SqlDbType.Int
});
cmd.Parameters.Add(new SqlParameter(){
    Direction = ParameterDirection.ReturnValue
});
cmd.CommandType = CommandType.StoredProcedure;

Будет производить:

-- BEGIN COMMAND
DECLARE @foobar INT = 1;
DECLARE @outParam INT = NULL;
DECLARE @returnValue INT;
-- END PARAMS
EXEC @returnValue = GetEntity @foobar = @foobar, @outParam = @outParam OUTPUT
-- RESULTS
SELECT 1 as Executed, @returnValue as ReturnValue, @outParam as [@outParam];
-- END COMMAND

Реализация:

public class SqlCommandDumper
{
    public static string GetCommandText(SqlCommand sqc)
    {
        StringBuilder sbCommandText = new StringBuilder();

        sbCommandText.AppendLine("-- BEGIN COMMAND");

        // params
        for (int i = 0; i < sqc.Parameters.Count; i++)
            logParameterToSqlBatch(sqc.Parameters[i], sbCommandText);
        sbCommandText.AppendLine("-- END PARAMS");

        // command
        if (sqc.CommandType == CommandType.StoredProcedure)
        {
            sbCommandText.Append("EXEC ");

            bool hasReturnValue = false;
            for (int i = 0; i < sqc.Parameters.Count; i++)
            {
                if (sqc.Parameters[i].Direction == ParameterDirection.ReturnValue)
                    hasReturnValue = true;
            }
            if (hasReturnValue)
            {
                sbCommandText.Append("@returnValue = ");
            }

            sbCommandText.Append(sqc.CommandText);

            bool hasPrev = false;
            for (int i = 0; i < sqc.Parameters.Count; i++)
            {
                var cParam = sqc.Parameters[i];
                if (cParam.Direction != ParameterDirection.ReturnValue)
                {
                    if (hasPrev)
                        sbCommandText.Append(", ");

                    sbCommandText.Append(cParam.ParameterName);
                    sbCommandText.Append(" = ");
                    sbCommandText.Append(cParam.ParameterName);

                    if (cParam.Direction.HasFlag(ParameterDirection.Output))
                        sbCommandText.Append(" OUTPUT");

                    hasPrev = true;
                }
            }
        }
        else
        {
            sbCommandText.AppendLine(sqc.CommandText);
        }

        sbCommandText.AppendLine("-- RESULTS");
        sbCommandText.Append("SELECT 1 as Executed");
        for (int i = 0; i < sqc.Parameters.Count; i++)
        {
            var cParam = sqc.Parameters[i];

            if (cParam.Direction == ParameterDirection.ReturnValue)
            {
                sbCommandText.Append(", @returnValue as ReturnValue");
            }
            else if (cParam.Direction.HasFlag(ParameterDirection.Output))
            {
                sbCommandText.Append(", ");
                sbCommandText.Append(cParam.ParameterName);
                sbCommandText.Append(" as [");
                sbCommandText.Append(cParam.ParameterName);
                sbCommandText.Append(']');
            }
        }
        sbCommandText.AppendLine(";");

        sbCommandText.AppendLine("-- END COMMAND");
        return sbCommandText.ToString();
    }

    private static void logParameterToSqlBatch(SqlParameter param, StringBuilder sbCommandText)
    {
        sbCommandText.Append("DECLARE ");
        if (param.Direction == ParameterDirection.ReturnValue)
        {
            sbCommandText.AppendLine("@returnValue INT;");
        }
        else
        {
            sbCommandText.Append(param.ParameterName);

            sbCommandText.Append(' ');
            if (param.SqlDbType != SqlDbType.Structured)
            {
                logParameterType(param, sbCommandText);
                sbCommandText.Append(" = ");
                logQuotedParameterValue(param.Value, sbCommandText);

                sbCommandText.AppendLine(";");
            }
            else
            {
                logStructuredParameter(param, sbCommandText);
            }
        }
    }

    private static void logStructuredParameter(SqlParameter param, StringBuilder sbCommandText)
    {
        sbCommandText.AppendLine(" {List Type};");
        var dataTable = (DataTable)param.Value;

        for (int rowNo = 0; rowNo < dataTable.Rows.Count; rowNo++)
        {
            sbCommandText.Append("INSERT INTO ");
            sbCommandText.Append(param.ParameterName);
            sbCommandText.Append(" VALUES (");

            bool hasPrev = false;
            for (int colNo = 0; colNo < dataTable.Columns.Count; colNo++)
            {
                if (hasPrev)
                {
                    sbCommandText.Append(", ");
                }
                logQuotedParameterValue(dataTable.Rows[rowNo].ItemArray[colNo], sbCommandText);
                hasPrev = true;
            }
            sbCommandText.AppendLine(");");
        }
    }

    const string DATETIME_FORMAT_ROUNDTRIP = "o";
    private static void logQuotedParameterValue(object value, StringBuilder sbCommandText)
    {
        try
        {
            if (value == null)
            {
                sbCommandText.Append("NULL");
            }
            else
            {
                value = unboxNullable(value);

                if (value is string
                    || value is char
                    || value is char[]
                    || value is System.Xml.Linq.XElement
                    || value is System.Xml.Linq.XDocument)
                {
                    sbCommandText.Append("N'");
                    sbCommandText.Append(value.ToString().Replace("'", "''"));
                    sbCommandText.Append('\'');
                }
                else if (value is bool)
                {
                    // True -> 1, False -> 0
                    sbCommandText.Append(Convert.ToInt32(value));
                }
                else if (value is sbyte
                    || value is byte
                    || value is short
                    || value is ushort
                    || value is int
                    || value is uint
                    || value is long
                    || value is ulong
                    || value is float
                    || value is double
                    || value is decimal)
                {
                    sbCommandText.Append(value.ToString());
                }
                else if (value is DateTime)
                {
                    // SQL Server only supports ISO8601 with 3 digit precision on datetime,
                    // datetime2 (>= SQL Server 2008) parses the .net format, and will 
                    // implicitly cast down to datetime.
                    // Alternatively, use the format string "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffK"
                    // to match SQL server parsing
                    sbCommandText.Append("CAST('");
                    sbCommandText.Append(((DateTime)value).ToString(DATETIME_FORMAT_ROUNDTRIP));
                    sbCommandText.Append("' as datetime2)");
                }
                else if (value is DateTimeOffset)
                {
                    sbCommandText.Append('\'');
                    sbCommandText.Append(((DateTimeOffset)value).ToString(DATETIME_FORMAT_ROUNDTRIP));
                    sbCommandText.Append('\'');
                }
                else if (value is Guid)
                {
                    sbCommandText.Append('\'');
                    sbCommandText.Append(((Guid)value).ToString());
                    sbCommandText.Append('\'');
                }
                else if (value is byte[])
                {
                    var data = (byte[])value;
                    if (data.Length == 0)
                    {
                        sbCommandText.Append("NULL");
                    }
                    else
                    {
                        sbCommandText.Append("0x");
                        for (int i = 0; i < data.Length; i++)
                        {
                            sbCommandText.Append(data[i].ToString("h2"));
                        }
                    }
                }
                else
                {
                    sbCommandText.Append("/* UNKNOWN DATATYPE: ");
                    sbCommandText.Append(value.GetType().ToString());
                    sbCommandText.Append(" *" + "/ N'");
                    sbCommandText.Append(value.ToString());
                    sbCommandText.Append('\'');
                }
            }
        }

        catch (Exception ex)
        {
            sbCommandText.AppendLine("/* Exception occurred while converting parameter: ");
            sbCommandText.AppendLine(ex.ToString());
            sbCommandText.AppendLine("*/");
        }
    }

    private static object unboxNullable(object value)
    {
        var typeOriginal = value.GetType();
        if (typeOriginal.IsGenericType
            && typeOriginal.GetGenericTypeDefinition() == typeof(Nullable<>))
        {
            // generic value, unboxing needed
            return typeOriginal.InvokeMember("GetValueOrDefault",
                System.Reflection.BindingFlags.Public |
                System.Reflection.BindingFlags.Instance |
                System.Reflection.BindingFlags.InvokeMethod,
                null, value, null);
        }
        else
        {
            return value;
        }
    }

    private static void logParameterType(SqlParameter param, StringBuilder sbCommandText)
    {
        switch (param.SqlDbType)
        {
            // variable length
            case SqlDbType.Char:
            case SqlDbType.NChar:
            case SqlDbType.Binary:
                {
                    sbCommandText.Append(param.SqlDbType.ToString().ToUpper());
                    sbCommandText.Append('(');
                    sbCommandText.Append(param.Size);
                    sbCommandText.Append(')');
                }
                break;
            case SqlDbType.VarChar:
            case SqlDbType.NVarChar:
            case SqlDbType.VarBinary:
                {
                    sbCommandText.Append(param.SqlDbType.ToString().ToUpper());
                    sbCommandText.Append("(MAX /* Specified as ");
                    sbCommandText.Append(param.Size);
                    sbCommandText.Append(" */)");
                }
                break;
            // fixed length
            case SqlDbType.Text:
            case SqlDbType.NText:
            case SqlDbType.Bit:
            case SqlDbType.TinyInt:
            case SqlDbType.SmallInt:
            case SqlDbType.Int:
            case SqlDbType.BigInt:
            case SqlDbType.SmallMoney:
            case SqlDbType.Money:
            case SqlDbType.Decimal:
            case SqlDbType.Real:
            case SqlDbType.Float:
            case SqlDbType.Date:
            case SqlDbType.DateTime:
            case SqlDbType.DateTime2:
            case SqlDbType.DateTimeOffset:
            case SqlDbType.UniqueIdentifier:
            case SqlDbType.Image:
                {
                    sbCommandText.Append(param.SqlDbType.ToString().ToUpper());
                }
                break;
            // Unknown
            case SqlDbType.Timestamp:
            default:
                {
                    sbCommandText.Append("/* UNKNOWN DATATYPE: ");
                    sbCommandText.Append(param.SqlDbType.ToString().ToUpper());
                    sbCommandText.Append(" *" + "/ ");
                    sbCommandText.Append(param.SqlDbType.ToString().ToUpper());
                }
                break;
        }
    }
}

Спасибо за это, это довольно всеобъемлющее! :-)
Аластер Мо

Именно то, что я искал, спасибо.
Xilmiki

Я использовал это как отправную точку для версии, которая использовала sp_executesql для обработки параметров в одном выражении, а не для объявления переменных по отдельности. Этот код действительно позаботился обо всей утомительной работе, и мне просто пришлось переставить кусочки. Большое спасибо!
Петтис

1
Разве для этого не требуется префикс «N» для строковых литералов SQL? В противном случае вы можете получить много "?" Молча. Плохой. (По крайней мере, с SQL Server 2005 - не проверял с менее древними версиями.)
Пол Грок

@PaulGroke, хороший улов. Я обновил, чтобы включить Nпрефикс.
Митч

6

У меня также была эта проблема, когда некоторые параметризованные запросы или sps давали бы мне SqlException (в основном это были бы усеченные строки или двоичные данные) и операторы, которые трудно отлаживать (насколько я знаю, в настоящее время нет поддержки sql-profiler для SQL Azure)

Я вижу много симуляционного кода в реакциях здесь. В итоге я поместил свое решение в проект библиотеки Sql для будущего использования.

Генератор доступен здесь: https://github.com/jeroenpot/SqlHelper/blob/master/Source/Mirabeau.MsSql.Library/SqlGenerator.cs

Он поддерживает как CommandType.Text, так и CommandType.StoredProcedure.

И если вы установите пакет nuget, вы можете сгенерировать его с помощью следующего оператора:

SqlDebugHelper.CreateExecutableSqlStatement(sql, parameters);

Не так уж плохо, он по крайней мере перечисляет значения для каждого параметра, но на самом деле не заполняет значения. По крайней мере, я могу использовать блокнот, чтобы сделать это самому, спасибо!
Харви Лин

5

Если вы используете SQL Server, вы можете использовать SQL Server Profiler (если он у вас есть) для просмотра фактически выполненной командной строки. Это было бы полезно для копирования копий / вставок, но не для регистрации.


3

Поздний ответ, я знаю, но я тоже хотел это, чтобы я мог войти в 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()

2

Профилировщик - ваш лучший вариант.

Вам может потребоваться скопировать набор операторов из профилировщика из-за включенных шагов подготовить + выполнить.


2

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

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

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


2

Для моего решения использовалась часть кода Flapper , которая возвращает всю строку SQL, включая значения параметров, для запуска в MS SQL SMS.

public string ParameterValueForSQL(SqlParameter sp)
    {
        string retval = "";

        switch (sp.SqlDbType)
        {
            case SqlDbType.Char:
            case SqlDbType.NChar:
            case SqlDbType.NText:
            case SqlDbType.NVarChar:
            case SqlDbType.Text:
            case SqlDbType.Time:
            case SqlDbType.VarChar:
            case SqlDbType.Xml:
            case SqlDbType.Date:
            case SqlDbType.DateTime:
            case SqlDbType.DateTime2:
            case SqlDbType.DateTimeOffset:
                if (sp.Value == DBNull.Value)
                {
                    retval = "NULL";
                }
                else
                {
                    retval = "'" + sp.Value.ToString().Replace("'", "''") + "'";
                }
                break;

            case SqlDbType.Bit:
                if (sp.Value == DBNull.Value)
                {
                    retval = "NULL";
                }
                else
                {
                    retval = ((bool)sp.Value == false) ? "0" : "1";
                }
                break;

            default:
                if (sp.Value == DBNull.Value)
                {
                    retval = "NULL";
                }
                else
                {
                    retval = sp.Value.ToString().Replace("'", "''");
                }
                break;
        }

        return retval;
    }


    public string CommandAsSql(SqlCommand sc)
    {
        string sql = sc.CommandText;

        sql = sql.Replace("\r\n", "").Replace("\r", "").Replace("\n", "");
        sql = System.Text.RegularExpressions.Regex.Replace(sql, @"\s+", " ");

        foreach (SqlParameter sp in sc.Parameters)
        {
            string spName = sp.ParameterName;
            string spValue = ParameterValueForSQL(sp);
            sql = sql.Replace(spName, spValue);
        }

        sql = sql.Replace("= NULL", "IS NULL");
        sql = sql.Replace("!= NULL", "IS NOT NULL");
        return sql;
    }

Ваше «решение» не работает. Вы заменили \ r и \ n на "", когда должны были использовать "". Кроме того, он не работает, если у вас более 9 параметров, так как замена '@ p1' заменяет '@ p1' и '@ p10' на все безумные результаты. Копирование списка параметров и обращение его назад было быстрым решением для того, что я делаю.
BH

Кроме того, ваш код не будет работать для команды обновления из-за замены 'is null'.
BH

на самом деле код Flapper не обрабатывает DBNull, здесь есть проблема для библиотеки CommandAsSQL, которая основана на нем: github.com/jphellemons/CommandAsSql/issues/1
Джордж Бирбилис

2

Мое решение:

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()));
    }
}

2

Я написал этот метод для меня. Я использую некоторую часть кода Бруно Ратниекса . Может быть, это кому-то пригодится.

 public static string getQueryFromCommand(SqlCommand cmd)
    {
        StringBuilder CommandTxt = new StringBuilder();
        CommandTxt.Append("DECLARE ");
        List<string> paramlst = new List<string>();
        foreach (SqlParameter parms in cmd.Parameters)
        {
            paramlst.Add(parms.ParameterName);
            CommandTxt.Append(parms.ParameterName + " AS ");
            CommandTxt.Append(parms.SqlDbType.ToString());
            CommandTxt.Append(",");
        }

        if (CommandTxt.ToString().Substring(CommandTxt.Length-1, 1) == ",")
            CommandTxt.Remove(CommandTxt.Length-1, 1);
        CommandTxt.AppendLine();
        int rownr = 0;
        foreach (SqlParameter parms in cmd.Parameters)
        {
            string val = String.Empty;
            if (parms.DbType.Equals(DbType.String) || parms.DbType.Equals(DbType.DateTime))
                val = "'" + Convert.ToString(parms.Value).Replace(@"\", @"\\").Replace("'", @"\'") + "'";
            if (parms.DbType.Equals(DbType.Int16) || parms.DbType.Equals(DbType.Int32) || parms.DbType.Equals(DbType.Int64) || parms.DbType.Equals(DbType.Decimal) || parms.DbType.Equals(DbType.Double))
                val = Convert.ToString(parms.Value);

            CommandTxt.AppendLine();
            CommandTxt.Append("SET " + paramlst[rownr].ToString() + " = " + val.ToString());
            rownr += 1;
        }
        CommandTxt.AppendLine();
        CommandTxt.AppendLine();
        CommandTxt.Append(cmd.CommandText);
        return CommandTxt.ToString();
    }

1

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

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

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


1

Это то, что я использую для вывода списков параметров для хранимой процедуры в консоль отладки:

string query = (from SqlParameter p in sqlCmd.Parameters where p != null where p.Value != null select string.Format("Param: {0} = {1},  ", p.ParameterName, p.Value.ToString())).Aggregate(sqlCmd.CommandText, (current, parameter) => current + parameter);
Debug.WriteLine(query);

Это сгенерирует выходной файл консоли:

Customer.prGetCustomerDetails: @Offset = 1,  Param: @Fetch = 10,  Param: @CategoryLevel1ID = 3,  Param: @VehicleLineID = 9,  Param: @SalesCode1 = bce,  

Я размещаю этот код непосредственно под любой процедурой, которую я хочу отладить, и похож на сеанс SQL Server Profiler, но в C #.


1

Модифицированная версия ответа Кона, поскольку она только частично работает с аналогичными именованными параметрами. Недостаток использования функции 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

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

    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

Как упоминали @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'

0

запросы команды 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

Мне нужно было охватить и не хранимые процедуры, поэтому я добавил библиотеку 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, для обработки таких проблем, как значения строковых параметров в одинарных кавычках и т. д.


Кстати, другой многообещающий фрагмент кода находится по адресу github.com/jeroenpot/SqlHelper/blob/master/Source/… (упоминается в ответе в этой теме). Вероятно, можно объединить код из SQLCommand и SqlGenerator, если вы обнаружите, что что-то не работает ни на одном, ни на другом
Джордж Бирбилис

... в последнем комментарии говорилось о библиотеке CommandAsSQL вместо SQLCommand
Джордж Бирбилис

0

Если вы будете конвертировать командный текст:

Private Function ConvToNonParm(ByRef Cmd As SqlClient.SqlCommand) As String
    For myCnt As Int16 = 1 To Cmd.Parameters.Count
        Dim myVal As String = Cmd.Parameters(myCnt - 1).Value
        Select Case Cmd.Parameters(myCnt - 1).SqlDbType
            Case SqlDbType.Char, SqlDbType.NChar, SqlDbType.VarChar, SqlDbType.NChar, SqlDbType.NVarChar 'and so on
                myVal = "'" & myVal & "'"
                'Case "others...."

            Case Else
                'please assing
        End Select
        Cmd.CommandText = Replace(Cmd.CommandText, Cmd.Parameters(myCnt - 1).ToString, myVal)
    Next
    Cmd.Parameters.Clear()
    Return Cmd.CommandText
End Function

Теперь вы можете получить командный текст без параметров следующим образом:

    myCmd.CommandText = "UPDATE someTable SET Value = @Value"
    myCmd.CommandText &= " WHERE Id = @Id"
    myCmd.Parameters.AddWithValue("@Id", 1234)
    myCmd.Parameters.AddWithValue("@Value", "myValue")

    myCmd.CommandText = ConvToNonParm(myCmd)

и результатом будет «UPDATE someTable SET Value = 'myValue' WHERE Id = 1234" без параметра больше


0

Расширенный код Кона для отладки хранимой процедуры:

    private void ExtractSqlCommandForDebugging(SqlCommand cmd)
    {
        string sql = "exec " + cmd.CommandText;
        bool first = true;
        foreach (SqlParameter p in cmd.Parameters)
        {
            string value = ((p.Value == DBNull.Value) ? "null"
                            : (p.Value is string) ? "'" + p.Value + "'"
                            : p.Value.ToString());
            if (first)
            {
                sql += string.Format(" {0}={1}", p.ParameterName, value);
                first = false;
            }
            else
            {
                sql += string.Format("\n , {0}={1}", p.ParameterName, value);
            }
        }
        sql += "\nGO";
        Debug.WriteLine(sql);
    }

В моем первом тестовом примере он сгенерировал:

exec dbo.MyStoredProcName @SnailMail=False
 , @Email=True
 , @AcceptSnailMail=False
 , @AcceptEmail=False
 , @DistanceMiles=-1
 , @DistanceLocationList=''
 , @ExcludeDissatisfied=True
 , @ExcludeCodeRed=True
 , @MinAge=null
 , @MaxAge=18
 , @GenderTypeID=-1
 , @NewThisYear=-1
 , @RegisteredThisYear=-1
 , @FormersTermGroupList=''
 , @RegistrationStartDate=null
 , @RegistrationEndDate=null
 , @DivisionList='25'
 , @LocationList='29,30'
 , @OneOnOneOPL=-1
 , @JumpStart=-1
 , @SmallGroup=-1
 , @PurchasedEAP=-1
 , @RedeemedEAP=-1
 , @ReturnPlanYes=False
 , @MinNetPromoter=-1
 , @MinSurveyScore=-1
 , @VIPExclusionTypes='-2'
 , @FieldSelectionMask=65011584
 , @DisplayType=0
GO

Возможно, вам потребуется добавить еще несколько условных присваиваний типа "..is ...", например, для дат и времени.



-1

От команды параметра к команде без параметра, Вы можете изменить это

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

к

Private sub Update( byval myID as Int32, byval myVal as String)
    Using cmd As SqlCommand = Connection.CreateCommand
        cmd.CommandText = "UPDATE someTable SET Value = '" & myVaL & "'" & _
                          " WHERE Id = " & myID  
        cmd.ExecuteNonQuery
    End Using
End sub
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.