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

У меня такой код:

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

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

Kiquenet 28.06.2016 13:47

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

dummy 28.06.2016 15:37

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

Rory 14.11.2016 01:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
197
3
173 139
24
Перейти к ответу Данный вопрос помечен как решенный

Ответы 24

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

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

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

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

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

kpkpkp 21.07.2017 00:17

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

George Birbilis 19.09.2018 19:40

Profiler - лучший вариант.

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

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

string query = cmd.CommandText;

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

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

dummy 05.11.2008 17:37

манекен: не совсем. если вы выполните подготовленный оператор, вы рискуете подвергнуться атаке с использованием sql-инъекции. +1 за ответ.

Sunny Milenov 05.11.2008 18:05

Если я заменю в своем примере @value на someString, он не будет цитироваться. По поводу sql-инъекции: я полностью с вами, я не хочу доморощенного решения. Спасибо, в любом случае. +1 на ваше время.

dummy 05.11.2008 20:16

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

Alok 30.08.2012 08:47

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

Kon 11.03.2013 18:49

Я наткнулся на это и наткнулся (просто мне повезло) на то, что упоминает @Alok. Есть ли способ сделать так, чтобы функция замены «соответствовала только целому слову»?

pkExec 13.03.2013 17:58

@Kon добавил измененную версию вашего ответа. Обрабатывает параметры с похожими именами, когда Replace просто их ломает.

HouseCat 16.03.2015 23:54

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

stambikk 17.08.2015 16:01

К сожалению, @ "\ b @ param \ b" не работает. Однако выход есть: stackoverflow.com/a/2544643/1970317

EvZ 07.03.2016 15:25

@ Что насчет поля blob или массива байтов

Smith 25.07.2016 20:10

Самое простое решение проблемы с регулярным выражением - использовать \ B (заглавная B) в начале регулярного выражения и \ b (маленький b) в конце, как описано в разделе «Обновление:» stackoverflow.com/a/2544661/903783.

George Birbilis 19.09.2018 18:49
Ответ принят как подходящий

Хотя это и не идеально, вот кое-что, что я придумал для 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);

Хорошая работа, на самом деле попытка решить проблему здесь, за одни только усилия.

Adam Tolley 05.04.2011 19:36

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

Benoittr 23.10.2013 20:41

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

Alexandre Marcondes 04.11.2013 17:33

@flapper что такое поле blob или массив байтов

Smith 25.07.2016 20:09

@Smith см. Пример форумы.asp.net/t/… - затем обновите операторы case по мере необходимости

Flapper 22.09.2016 14:34

У меня были параметры без @. Как это: cmd.Parameters.AddWithValue("Id", 1234), которые работают в коде. Но вывод CommandAsSql не будет пастируемым / исполняемым в SSMS из-за отсутствия @, а с плавающей запятой ToString() дал мне запятую вместо точки в качестве десятичного разделителя, поскольку он зависит от культуры. Это вызвало ошибку (для меня) Возможно, это дополнение сэкономит время некоторым людям :)

JP Hellemons 27.07.2017 13:00

Внесены незначительные изменения и добавлены параметры табличных значений. Все это есть на GitHub и Nuget-пакете .Net Standard 2.0 github.com/jphellemons/CommandAsSql Спасибо, Flapper! Могу я добавить вас в качестве соавтора?

JP Hellemons 25.08.2017 17:27

@JPHellemons, не стесняйтесь :) Я FlapperMK на github

Flapper 05.09.2017 17:00

Я отправил PR, который реализует случай, когда типом SQLCommand является текст, его можно найти здесь: github.com/jphellemons/CommandAsSql/pull/3/commit/…

George Birbilis 19.09.2018 18:45

чувствуете ли вы retval = "'" + sp.Value.ToString (). Replace ("'", "''") + "'"; правильно для DateTime? Я получаю локализованные даты на греческом языке, когда использую их через библиотеку SQLCommand. В коде библиотеки CommandAsSQL он также использует позже sb.Append ("'"). Append (Convert.ToDateTime (dr [colIndex]). ToStr‌ ing ("yyyy-MM-dd HH: mm")). Append ( "'"); для обработки типов структур, которые кажутся более логичными для использования везде - я имею в виду .ToString ("yyyy-MM-dd HH: mm") - не уверен насчет DateTimeOffset и DateTime2, угадайте и для них

George Birbilis 19.09.2018 19:22

... ой, предназначен для написания библиотеки CommandAsSQL в комментарии выше

George Birbilis 19.09.2018 19:37

Стоит отметить, что текстовая команда может (и должна) принимать параметры, а не только хранимые процедуры.

apc 21.09.2020 18:01

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

    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

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

Пример:

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

Спасибо за это, он довольно подробный! :-)

Alastair Maw 30.05.2014 15:21

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

Xilmiki 14.08.2015 14:09

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

pettys 19.05.2016 18:02

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

Paul Groke 10.08.2016 23:05

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

Mitch 11.08.2016 03:29

@Mitch, отлично :) Еще кое-что, что я только что заметил: value.ToString() для float, double, decimal может быть проблематичным, если текущая локаль использует "," в качестве десятичного знака. Также я не уверен, что ToString без параметров создает представление, которое точно выполняет циклическую обработку (некоторые реализации усекают / округляют таким образом, чтобы по умолчанию жертвовать LSB - не уверен, что делает .NET).

Paul Groke 11.08.2016 18:45

@PaulGroke, мне неизвестен десятичный разделитель, нечувствительный к культуре для T-SQL, поэтому я предполагаю, что текущая культура совпадает с языком пользователя SQL. Поскольку существует круговой обход текста, я ожидаю некоторой потери точности в значениях с плавающей запятой. Я также не уверен, как он будет обрабатывать NaN, Inf, -0 и другие значения.

Mitch 11.08.2016 19:15

@Mitch: AFAIK SQL Server всегда использует (и ожидает) десятичную точку, а не запятую. Я ошибаюсь? Также должно работать круговое отключение нормальных значений. Я думаю, что ToString("G17", CultureInfo.InvariantCulture) подойдет для float и double, а ToString("G", CultureInfo.InvariantCulture) - для decimal. (MSDN утверждает, что формат «R» не всегда означает двустороннюю передачу double в системах x64. Это довольно мило, потому что «R» на самом деле означает «туда и обратно».) Однако особые значения представляют собой проблему. Но опять же, похоже, что у SQL Server с ними проблемы.

Paul Groke 12.08.2016 03:03

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

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

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

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

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

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

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

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, но на C#.

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

Используемая часть Код Хлопушки для моего решения, которое возвращает всю строку 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» на всевозможные сумасшедшие результаты. Копирование списка параметров и его реверсирование было быстрым решением для того, что я делаю.

B H 02.05.2018 21:57

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

B H 02.05.2018 22:03

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

George Birbilis 19.09.2018 20:01

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

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

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

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

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

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

SqlDebugHelper.CreateExecutableSqlStatement(sql, parameters);

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

Harvey Lin 21.01.2019 22:38

Мое решение:

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

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

Один лайнер:

string.Join(",", from SqlParameter p in cmd.Parameters select p.ToString()) 

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

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

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

George Birbilis 19.09.2018 18:59

... имел в виду библиотеку CommandAsSQL вместо SQLCommand в последнем комментарии

George Birbilis 19.09.2018 19:48

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

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

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

Это не отвечает на вопрос о преобразовании универсального объекта SqlCommand в строку, он заменяет его жестко закодированными строками. Он также ВЫПОЛНЯЕТ полученную строку вместо того, чтобы возвращать ее (например, для ведения журнала) - такая строка никогда не должна выполняться и должна считаться уязвимой для SQL-инъекций.

Ekus 25.08.2020 23:40

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

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" без параметра больше

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

    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 ...", например для даты и времени.

Если ваша база данных была Oracle, а текст sql содержит динамические переменные с именем :1,:2 ,..., вы можете использовать:

string query = cmd.CommandText;
int i = 1;
foreach (OracleParameter p in cmd.Parameters)
  {
    query = query.Replace(":"+i.ToString(),((p.Value==null)?"":p.Value.ToString()));
    i++;
  }

Другие вопросы по теме