Die generierte SQL-Anweisung von einem SqlCommand-Objekt abrufen?

186

Ich habe folgenden Code:

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

Ich frage mich, ob es eine Möglichkeit gibt, die endgültige SQL-Anweisung als String abzurufen, die folgendermaßen aussehen sollte:

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

Wenn sich jemand fragt, warum ich das tun würde:

  • zum Protokollieren von (fehlgeschlagenen) Anweisungen
  • für die Möglichkeit, es zu Testzwecken zu kopieren und in den Enterprise Manager einzufügen
Dummy
quelle
1
Warum haben Sie die Antwort stackoverflow.com/a/265261/206730 markiert, wenn Sie nicht zwischen verschiedenen Datentypen, SQL-Injection und ähnlichen Parameternamen unterscheiden (Problem ersetzen) ...?
Kiquenet
@Kiquenet Ich hätte schwören können, dass ich das versucht habe, aber es hat mich nicht gelassen. Jetzt gehts. Danke dafür.
Dummy
Wenn Sie die SQL, die ausgeführt werden soll, genau generieren möchten, schauen Sie sich TdsParser.TdsExecuteRPC ( github.com/Microsoft/referencesource/blob/master/System.Data/… ) an und haben Sie ein wenig Angst.
Rory

Antworten:

110

Obwohl es nicht perfekt ist, habe ich mich für TSQL entschieden - könnte leicht für andere Geschmacksrichtungen optimiert werden ... Wenn nichts anderes, gibt es Ihnen einen Ausgangspunkt für Ihre eigenen Verbesserungen :)

Dies führt einen OK-Job für Datentypen und Ausgabeparameter usw. aus, ähnlich wie bei der Verwendung von "Gespeicherte Prozedur ausführen" in SSMS. Wir haben meistens SPs verwendet, damit der Befehl "text" keine Parameter usw. Berücksichtigt

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

Dies erzeugt eine Ausgabe in diese Richtung ...

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);
Flapper
quelle
7
Gute Arbeit, die tatsächlich versucht, das Problem hier anzugehen, allein für ihre Bemühungen gestimmt.
Adam Tolley
3
Was wäre Ihre "ToBooleanOrDefault (false)" - Methode?
Benoittr
6
@ Benoittr, Sie können eine Implementierung von ToBooleanOrDefaulthier sehen: Frage # 3244850
Alexandre Marcondes
@ Flapper was ist mit einem Blob-Feld oder Byte-Array
Smith
1
Einige geringfügige Anpassungen vorgenommen und Tabellenwertparameter hinzugefügt. Es ist alles auf GitHub und einem .Net Standard 2.0 Nuget-Paket github.com/jphellemons/CommandAsSql. Vielen Dank, Flapper! Kann ich Sie als Mitarbeiter hinzufügen?
JP Hellemons
128

Für die Protokollierung gibt es leider keinen besseren Weg, dies zu tun, als den String selbst zu erstellen:

string query = cmd.CommandText;

foreach (SqlParameter p in cmd.Parameters)
{
    query = query.Replace(p.ParameterName, p.Value.ToString());
}
Kon
quelle
Wenn ich das mache, muss ich zwischen verschiedenen Datentypen unterscheiden. Dann könnte ich die parametrisierte Abfrage alle zusammen überspringen und das ausführen.
Dummy
2
Dummy: nicht wirklich. Wenn Sie eine vorbereitete Anweisung ausführen, besteht das Risiko eines SQL-Injection-Angriffs. +1 für die Antwort.
Sunny Milenov
11
Theres ein gotcha hier. Wenn ich "Param" und "differentParam" als Parameter habe, wird das differentParam unbrauchbar, da es durch "ValueParam" ersetzt wird. unter der Annahme von Param = Wert.
Alok
5
Die Frage befasst sich nicht mit defensiven Codierungstechniken, daher sind Nullreferenzprüfungen nicht Teil der Antwort. Die Tatsache, dass es implementiert werden sollte, ist impliziert, daher sehe ich dies nicht als konstruktiven Kommentar.
Kon
2
Ein etwas besserer Ansatz, um das Problem mit ähnlichen Parameternamen zu beseitigen, auf die @Alok hingewiesen hat, könnte darin bestehen, query = Regex.Replace(query, @"\b" + p.ParameterName + @"\b", p.Value.ToString());die Parameter in der Zeichenfolge zu ersetzen. Dies wird das "ganze Wort" ersetzen. Dies ist jedoch möglicherweise keine universelle Lösung, da \ b eine Position zwischen einem Wortzeichen und einem Nichtwortzeichen markiert. p.ParameterName + @"\b"Wenn Ihre Parameternamen also mit @ beginnen, sollten Sie den Parameter in der Abfragezeichenfolge ersetzen.
Stambikk
47

Sie können nicht, weil es kein SQL generiert.

Die parametrisierte Abfrage (die in CommandText ) wird als Äquivalent einer vorbereiteten Anweisung an den SQL Server gesendet. Wenn Sie den Befehl ausführen, werden die Parameter und der Abfragetext separat behandelt. Zu keinem Zeitpunkt wird eine vollständige SQL-Zeichenfolge generiert.

Mit SQL Profiler können Sie einen Blick hinter die Kulissen werfen.

Tomalak
quelle
6
SQL wird generiert - siehe Profiler - das ist der Text, den ich für Protokollierungszwecke haben möchte
kpkpkp
Abgesehen von SQL Profiler (der für neuere SQL Server veraltet ist, wenn ich einen MS-Kommentar richtig verstanden habe) kann Activity Monitor auch gemäß einer anderen Antwort hier verwendet werden
George Birbilis
27

Ich brauchte einen ähnlichen Befehl wie String String Transformator, um eine ausführlichere Protokollierung zu ermöglichen, also habe ich diesen geschrieben. Es wird der Text erzeugt, der zum erneuten Ausführen des Befehls in einer neuen Sitzung erforderlich ist, einschließlich Ausgabeparametern und strukturierten Parametern. Es ist leicht getestet, aber Vorbehalt Emptor.

Beispiel:

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;

Wird herstellen:

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

Implementierung:

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;
        }
    }
}
Mitch
quelle
Danke dafür, es ist ziemlich umfassend! :-)
Alastair Maw
Genau das, wonach ich gesucht habe, danke.
Xilmiki
Ich habe dies als Ausgangspunkt für eine Version verwendet, die sp_executesql verwendet hat, um die Parameter in einer einzelnen Anweisung zu behandeln, anstatt Variablen separat zu deklarieren. Dieser Code hat sich wirklich um die mühsame Arbeit gekümmert und ich musste nur die Teile neu anordnen. Vielen Dank!
Pettys
1
Benötigt dies nicht ein "N" -Präfix für die SQL-String-Literale? Andernfalls könnten Sie viele "?" Schweigend. Schlecht. (Zumindest mit SQL Server 2005 - habe nicht mit weniger alten Versionen überprüft.)
Paul Groke
@ PaulGroke, guter Fang. Ich habe aktualisiert, um das NPräfix aufzunehmen.
Mitch
6

Ich hatte auch dieses Problem, bei dem einige parametrisierte Abfragen oder SPs mir eine SqlException gaben (meistens wurden die Zeichenfolgen oder Binärdaten abgeschnitten), und die Anweisungen waren schwer zu debuggen (soweit ich weiß, gibt es derzeit keine Unterstützung für SQL-Profiler SQL Azure)

Ich sehe hier viel ähnlichen Code in Reaktionen. Am Ende habe ich meine Lösung für die zukünftige Verwendung in ein SQL-Library-Projekt gestellt.

Der Generator ist hier verfügbar: https://github.com/jeroenpot/SqlHelper/blob/master/Source/Mirabeau.MsSql.Library/SqlGenerator.cs

Es unterstützt sowohl CommandType.Text als auch CommandType.StoredProcedure

Und wenn Sie das Nuget-Paket installieren , können Sie es mit folgender Anweisung generieren:

SqlDebugHelper.CreateExecutableSqlStatement(sql, parameters);
Jeroen Pot
quelle
Nicht schlecht, es listet zumindest die Werte für jeden Parameter auf, füllt die Werte aber immer noch nicht aus. Zumindest kann ich das mit dem Notizblock selbst machen, danke!
Harvey Lin
5

Wenn Sie SQL Server verwenden, können Sie den SQL Server Profiler (falls vorhanden) verwenden, um die tatsächlich ausgeführte Befehlszeichenfolge anzuzeigen. Das wäre nützlich zum Testen / Einfügen von Testzwecken, aber leider nicht zum Protokollieren.

Rockcoder
quelle
3

Späte Antwort, ich weiß, aber ich wollte das auch, damit ich die SQL protokollieren kann. Das Folgende ist kurz und entspricht meinen Bedürfnissen.

Im Folgenden wird SQL erstellt, das Sie in SSMS kopieren / einfügen können (es ersetzt die Parameter ordnungsgemäß durch die Werte). Sie können weitere Typen hinzufügen, aber dies entspricht allem, was ich in diesem Fall verwende.

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

Jetzt kann ich SQL protokollieren, bevor ich es ausführe:

LogSQL(queryCmd)
queryCmd.ExecuteNonQuery()
Paul Sturm
quelle
2

Profiler ist zweifellos die beste Option.

Aufgrund der Schritte zum Vorbereiten und Ausführen müssen Sie möglicherweise eine Reihe von Anweisungen aus dem Profiler kopieren.

Ed Guiness
quelle
2

Ich hatte genau die gleiche Frage und nachdem ich diese Antworten gelesen hatte, entschied ich fälschlicherweise, dass es nicht möglich war, die genaue resultierende Abfrage zu erhalten. Ich lag falsch.

Lösung: Öffnen Sie Activity Monitorin SQL Server Management Studioverengen sich die Prozesse Abschnitt auf die Login - Benutzernamen, Datenbank- oder Anwendungsnamen , dass Ihre Anwendung in der Verbindungszeichenfolge verwendet. Wenn der Aufruf der Datenbankaktualisierung erfolgt Activity Monitor. Wenn Sie den Vorgang sehen, klicken Sie mit der rechten Maustaste darauf und View Details.

Beachten Sie, dass dies möglicherweise keine praktikable Option für eine ausgelastete Datenbank ist. Mit diesen Schritten sollten Sie das Ergebnis jedoch erheblich eingrenzen können.

Alan
quelle
2

Verwendet einen Teil von Flappers Code für meine Lösung, der die gesamte SQL-Zeichenfolge einschließlich der Parameterwerte zurückgibt, die in MS SQL SMS ausgeführt werden sollen.

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;
    }
Barry-Dean
quelle
Ihre 'Lösung' funktioniert nicht. Sie haben \ r und \ n durch "" ersetzt, wenn Sie "" hätten verwenden sollen. Außerdem funktioniert es nicht, wenn Sie mehr als 9 Parameter haben, da das Ersetzen von '@ p1' sowohl '@ p1' als auch '@ p10' durch alle möglichen verrückten Ergebnisse ersetzt. Das Kopieren und Umkehren der Parameterliste war eine schnelle Lösung für das, was ich tue.
BH
Außerdem funktioniert Ihr Code für einen Aktualisierungsbefehl nicht, da 'is null' ersetzt wird.
BH
In der Tat behandelt Flappers Code DBNull nicht. Hier gibt es ein Problem mit der darauf basierenden CommandAsSQL-Bibliothek: github.com/jphellemons/CommandAsSql/issues/1
George Birbilis
2

Meine Lösung:

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()));
    }
}
Martin.Martinsson
quelle
2

Ich habe diese Methode für mich geschrieben. Ich benutze einen Teil von Bruno Ratnieks Code. Vielleicht ist es für jemanden nützlich.

 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();
    }
Daghan Karakasoglu
quelle
1

Wenn nur überprüft werden soll, wie ein Parameter in der Ergebnisabfrage formatiert ist, können die meisten DBMS Literale aus dem Nichts abfragen. So:

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

Auf diese Weise können Sie sehen, ob Anführungszeichen verdoppelt werden usw.

MPelletier
quelle
1

Dies ist, was ich verwende, um Parameterlisten für eine gespeicherte Prozedur in der Debug-Konsole auszugeben:

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

Dadurch wird eine ähnliche Konsolenausgabe generiert:

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

Ich platziere diesen Code direkt unter jeder Prozedur, die ich debuggen möchte, und ähnelt einer SQL-Profiler-Sitzung, jedoch in C #.

Nocarrier
quelle
1

Geänderte Version der Antwort von Kon, da sie nur teilweise mit ähnlich benannten Parametern funktioniert. Die Kehrseite der Verwendung der Funktion "String ersetzen". Davon abgesehen gebe ich ihm die volle Anerkennung für die Lösung.

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;
}
Hauskatze
quelle
0

Diese Lösung funktioniert gerade für mich. Vielleicht ist es für jemanden nützlich. Bitte entschuldigen Sie die Redundanz.

    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
Dummy
quelle
0

Wie bei @pkExec und @Alok erwähnt, funktioniert die Verwendung von Ersetzen in 100% der Fälle nicht. Dies ist die Lösung, die ich in unserem DAL verwendet habe und die RegExp verwendet, um nur "ganze Wörter abzugleichen" und die Datentypen korrekt zu formatieren. Somit kann das generierte SQL direkt in MySQL Workbench (oder SQLSMS usw.) getestet werden :)

(Ersetzen Sie die Funktion MySQLHelper.EscapeString () gemäß dem verwendeten DBMS.)

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

Beispiel:

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

Wird generiert:

SELECT * FROM order WHERE order_status = 'C' AND order_date = '2015-01-01 00:00:00'
JotaSantana
quelle
0

Die SQL-Befehlsabfragen werden mit exec sp_executesql ausgeführt. Hier ist eine andere Möglichkeit, die Anweisung als Zeichenfolge abzurufen (SqlCommand-Erweiterungsmethode):

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;
}
o_link
quelle
0

musste auch nicht gespeicherte Prozeduren abdecken, daher habe ich die CommandAsSql-Bibliothek (siehe Kommentare unter @ Flappers Antwort oben) mit dieser Logik erweitert:

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

Die Pull-Anforderung lautet: https://github.com/jphellemons/CommandAsSql/pull/3/commits/527d696dc6055c5bcf858b9700b83dc863f04896

Die Regex-Idee basierte auf den obigen Kommentaren von @ stambikk und EvZ sowie dem Abschnitt "Update:" von https://stackoverflow.com/a/2544661/903783 , in dem "negative Look- Behind -Behauptung" erwähnt wird. Die Verwendung von \ B anstelle von \ b für die Erkennung von Wortgrenzen zu Beginn des regulären Ausdrucks liegt daran, dass der p.parameterName immer mit einem "@" beginnt, das kein Wortzeichen ist.

Beachten Sie, dass ParameterValueForSQL () eine Erweiterungsmethode ist, die in der CommandAsSql-Bibliothek definiert ist, um Probleme wie Zeichenfolgenparameterwerte in einfachen Anführungszeichen usw. zu behandeln.

George Birbilis
quelle
Übrigens ist ein weiterer vielversprechender Code unter github.com/jeroenpot/SqlHelper/blob/master/Source/… (bei einer Antwort in diesem Thread erwähnt). Wahrscheinlich könnte Code von SQLCommand und SqlGenerator zusammengeführt werden, wenn Sie etwas finden, das bei dem einen oder anderen nicht funktioniert
George Birbilis
... wollte im letzten Kommentar CommandAsSQL-Bibliothek anstelle von SQLCommand sagen
George Birbilis
0

Wenn Sie den Befehlstext konvertieren:

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

Jetzt können Sie den Nicht-Parameter-Befehlstext wie folgt abrufen:

    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)

und das Ergebnis ist "UPDATE someTable SET Value = 'myValue' WHERE Id = 1234" ohne Parameter mehr

user11982798
quelle
0

Der Code von Kon wurde erweitert , um das Debuggen einer gespeicherten Prozedur zu erleichtern:

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

In meinem ersten Testfall wurde Folgendes generiert:

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

Sie müssen wahrscheinlich weitere bedingte Typzuweisungen "..is ..." hinzufügen, z. B. für Datum und Uhrzeit.

CAK2
quelle
-1

Einzeiler:

string.Join(",", from SqlParameter p in cmd.Parameters select p.ToString()) 
CheesusCrust
quelle
-1

Vom Parameterbefehl zum Nichtparameterbefehl können Sie diesen ändern

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

Zu

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
user11982798
quelle