我有以下代码:
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语句作为String获取,它应该如下所示:
UPDATE someTable SET Value = "myValue" WHERE Id = 1234
如果有人想知道为什么我会这样做:
用于记录(失败)语句
有可能将其复制并粘贴到企业管理器以进行测试
Kon.. 118
出于记录的目的,我担心没有更好的方法可以做到这一点,而是自己构建字符串:
string query = cmd.CommandText; foreach (SqlParameter p in cmd.Parameters) { query = query.Replace(p.ParameterName, p.Value.ToString()); }
这里有一个问题.如果我有"Param"和"differentParam"作为参数,它会将differentParam替换为"ValueParam".假设Param = Value. (11认同)
这个问题不涉及防御性编码技术,因此空参考检查不是答案的一部分.应该实施它的事实是隐含的,因此我不认为这是一个建设性的评论. (5认同)
假的:不是真的.如果执行预准备语句,则存在sql注入攻击的风险.答案为+1. (2认同)
使用@Alok指出的类似param名称消除问题的稍微好一点的方法可能是使用`query = Regex.Replace(query,@"\ b"+ p.ParameterName + @"\ b",p. Value.ToString());`用于替换字符串中的参数.这将取代"整个单词".它可能不是一个通用的解决方案,因为\ b标记一个单词字符和一个非单词字符之间的位置,所以如果你的参数名称以@开头,你应该使用`p.ParameterName + @"\ b"`替换查询字符串中的参数. (2认同)
Flapper.. 101
虽然不完美,但这里有一些我为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认同)
@Benoittr,你可以在这里看到`ToBooleanOrDefault`的实现:[问题#3244850](http://stackoverflow.com/questions/3244850/visual-studio-2010-compiler-error-for-base-class-extensions) (5认同)
什么是你的"ToBooleanOrDefault(false)"方法? (3认同)
Tomalak.. 46
你不能,因为它不会生成任何SQL.
参数化查询(中的一个CommandText
)作为预准备语句的等效项发送到SQL Server.执行命令时,将分别处理参数和查询文本.在任何时候都不会生成完整的SQL字符串.
您可以使用SQL事件探查器来了解幕后.
出于记录的目的,我担心没有更好的方法可以做到这一点,而是自己构建字符串:
string query = cmd.CommandText; foreach (SqlParameter p in cmd.Parameters) { query = query.Replace(p.ParameterName, p.Value.ToString()); }
虽然不完美,但这里有一些我为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);
你不能,因为它不会生成任何SQL.
参数化查询(中的一个CommandText
)作为预准备语句的等效项发送到SQL Server.执行命令时,将分别处理参数和查询文本.在任何时候都不会生成完整的SQL字符串.
您可以使用SQL事件探查器来了解幕后.
我需要一个类似于字符串变换器的命令来允许更详细的日志记录,所以我写了这个.它将生成在新会话中重新执行命令所需的文本,包括输出参数和结构化参数.它经过了轻微的测试,但需要注意.
例:
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会给我一个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-package,您可以使用以下语句生成它:
SqlDebugHelper.CreateExecutableSqlStatement(sql, parameters);
如果您使用的是SQL Server,则可以使用SQL Server Profiler(如果有)查看实际执行的命令字符串.这对于复制/粘贴测试purpuses很有用,但对于我害怕的日志记录却没有用.