У меня такой код:
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
Если кому-то интересно, зачем мне это делать:
@Kiquenet Я мог бы поклясться, что я пробовал это, но мне это не удалось. Теперь это работает. Спасибо тебе за это.
Если вы хотите точно сгенерировать SQL, который будет запускаться, взгляните на TdsParser.TdsExecuteRPC (github.com/Microsoft/referencesource/blob/master/System.Dat a /…) и немного испугайтесь.





Если вы используете SQL Server, вы можете использовать SQL Server Profiler (если он у вас есть) для просмотра фактически выполняемой командной строки. Это было бы полезно для целей тестирования копирования / вставки, но, боюсь, не для ведения журнала.
Вы не можете, потому что он не генерирует никакого SQL.
Параметризованный запрос (в CommandText) отправляется на SQL Server как эквивалент подготовленного оператора. Когда вы выполняете команду, параметры и текст запроса обрабатываются отдельно. Ни в коем случае не создается полная строка SQL.
Вы можете использовать SQL Profiler, чтобы заглянуть за кулисы.
SQL генерируется - посмотрите в Profiler - это текст, который я хотел бы иметь для целей регистрации
помимо SQL Profiler (который устарел для нового SQL Server, если я правильно понял некоторые комментарии MS), также можно использовать Activity Monitor в соответствии с другим ответом здесь
Profiler - лучший вариант.
Вам может потребоваться скопировать набор операторов из профилировщика из-за задействованных шагов подготовки + выполнения.
Боюсь, что для ведения журнала нет лучшего способа сделать это, кроме как построить строку самостоятельно:
string query = cmd.CommandText;
foreach (SqlParameter p in cmd.Parameters)
{
query = query.Replace(p.ParameterName, p.Value.ToString());
}
Если я это сделаю, мне придется различать разные типы данных. Тогда я мог бы полностью пропустить параметризованный запрос и выполнить его.
манекен: не совсем. если вы выполните подготовленный оператор, вы рискуете подвергнуться атаке с использованием sql-инъекции. +1 за ответ.
Если я заменю в своем примере @value на someString, он не будет цитироваться. По поводу sql-инъекции: я полностью с вами, я не хочу доморощенного решения. Спасибо, в любом случае. +1 на ваше время.
Здесь есть ошибка. Если у меня есть параметры «Param» и «differentParam», это делает параметр differentParam бесполезным, поскольку он заменяет его на «ValueParam». при условии, что Param = Value.
Вопрос не касается методов защитного кодирования, поэтому проверка нулевых ссылок не является частью ответа. То, что это должно быть реализовано, подразумевается, поэтому я не считаю это конструктивным комментарием.
Я наткнулся на это и наткнулся (просто мне повезло) на то, что упоминает @Alok. Есть ли способ сделать так, чтобы функция замены «соответствовала только целому слову»?
@Kon добавил измененную версию вашего ответа. Обрабатывает параметры с похожими именами, когда Replace просто их ломает.
немного лучший подход к устранению проблемы с похожими именами параметров, указанными @Alok, может заключаться в использовании query = Regex.Replace(query, @"\b" + p.ParameterName + @"\b", p.Value.ToString()); для замены параметров в строке. Это заменит «целое слово». Возможно, это не универсальное решение, поскольку \ b отмечает позицию между символом слова и символом, не являющимся словом, поэтому в случае, если имена ваших параметров начинаются с @, вы должны использовать p.ParameterName + @"\b" для замены параметра в строке запроса.
К сожалению, @ "\ b @ param \ b" не работает. Однако выход есть: stackoverflow.com/a/2544643/1970317
@ Что насчет поля blob или массива байтов
Самое простое решение проблемы с регулярным выражением - использовать \ B (заглавная B) в начале регулярного выражения и \ b (маленький b) в конце, как описано в разделе «Обновление:» stackoverflow.com/a/2544661/903783.
Хотя это и не идеально, вот кое-что, что я придумал для 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);
Хорошая работа, на самом деле попытка решить проблему здесь, за одни только усилия.
Каким будет ваш метод ToBooleanOrDefault (false)?
@Benoittr, вы можете увидеть здесь реализацию ToBooleanOrDefault: Вопрос № 3244850
@flapper что такое поле blob или массив байтов
@Smith см. Пример форумы.asp.net/t/… - затем обновите операторы case по мере необходимости
У меня были параметры без @. Как это: cmd.Parameters.AddWithValue("Id", 1234), которые работают в коде. Но вывод CommandAsSql не будет пастируемым / исполняемым в SSMS из-за отсутствия @, а с плавающей запятой ToString() дал мне запятую вместо точки в качестве десятичного разделителя, поскольку он зависит от культуры. Это вызвало ошибку (для меня) Возможно, это дополнение сэкономит время некоторым людям :)
Внесены незначительные изменения и добавлены параметры табличных значений. Все это есть на GitHub и Nuget-пакете .Net Standard 2.0 github.com/jphellemons/CommandAsSql Спасибо, Flapper! Могу я добавить вас в качестве соавтора?
@JPHellemons, не стесняйтесь :) Я FlapperMK на github
Я отправил PR, который реализует случай, когда типом SQLCommand является текст, его можно найти здесь: github.com/jphellemons/CommandAsSql/pull/3/commit/…
чувствуете ли вы 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, угадайте и для них
... ой, предназначен для написания библиотеки CommandAsSQL в комментарии выше
Стоит отметить, что текстовая команда может (и должна) принимать параметры, а не только хранимые процедуры.
Это решение работает для меня прямо сейчас. Может кому пригодится. Прошу прощения за избыточность.
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;
}
}
}
Спасибо за это, он довольно подробный! :-)
Именно то, что я искал, спасибо.
Я использовал это как отправную точку для его версии, в которой sp_executesql использовалась для обработки параметров в одном операторе, а не для объявления переменных по отдельности. Этот код действительно позаботился обо всей утомительной работе, и мне просто нужно было переставить части. Большое спасибо!
Разве это не требует префикса «N» для строковых литералов SQL? В противном случае вы можете получить много "?" Молча. Плохой. (По крайней мере, с SQL Server 2005 - не проверял с менее древними версиями.)
@PaulGroke, хороший улов. Я обновился, чтобы включить префикс N.
@Mitch, отлично :) Еще кое-что, что я только что заметил: value.ToString() для float, double, decimal может быть проблематичным, если текущая локаль использует "," в качестве десятичного знака. Также я не уверен, что ToString без параметров создает представление, которое точно выполняет циклическую обработку (некоторые реализации усекают / округляют таким образом, чтобы по умолчанию жертвовать LSB - не уверен, что делает .NET).
@PaulGroke, мне неизвестен десятичный разделитель, нечувствительный к культуре для T-SQL, поэтому я предполагаю, что текущая культура совпадает с языком пользователя SQL. Поскольку существует круговой обход текста, я ожидаю некоторой потери точности в значениях с плавающей запятой. Я также не уверен, как он будет обрабатывать NaN, Inf, -0 и другие значения.
@Mitch: AFAIK SQL Server всегда использует (и ожидает) десятичную точку, а не запятую. Я ошибаюсь? Также должно работать круговое отключение нормальных значений. Я думаю, что ToString("G17", CultureInfo.InvariantCulture) подойдет для float и double, а ToString("G", CultureInfo.InvariantCulture) - для decimal. (MSDN утверждает, что формат «R» не всегда означает двустороннюю передачу double в системах x64. Это довольно мило, потому что «R» на самом деле означает «туда и обратно».) Однако особые значения представляют собой проблему. Но опять же, похоже, что у SQL Server с ними проблемы.
Если только проверить, как параметр отформатирован в запросе результата, большинство СУБД позволит запрашивать литералы из ничего. Таким образом:
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» на всевозможные сумасшедшие результаты. Копирование списка параметров и его реверсирование было быстрым решением для того, что я делаю.
Кроме того, ваш код не будет работать для команды обновления из-за замены "равно нулю".
действительно, код Flapper не обрабатывает DBNull, здесь есть проблема с библиотекой CommandAsSQL, основанной на нем: github.com/jphellemons/CommandAsSql/issues/1
Как упоминалось в @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);
Не так уж плохо, он, по крайней мере, перечисляет значения для каждого параметра, но на самом деле не заполняет значения. По крайней мере, я могу использовать для этого блокнот, спасибо!
Мое решение:
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, если вы обнаружите, что что-то не работает на одном или другом
... имел в виду библиотеку CommandAsSQL вместо SQLCommand в последнем комментарии
Поздний ответ, я знаю, но я тоже этого хотел, чтобы я мог регистрировать 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-инъекций.
Если вы преобразуете командный текст:
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++;
}
Почему вы отметили ответ stackoverflow.com/a/265261/206730, если не различаете разные типы данных, Sql Injection, имена параметров похожи (проблема замены) ...?