Pobierz wygenerowaną instrukcję SQL z obiektu SqlCommand?

186

Mam następujący kod:

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

Zastanawiam się, czy jest jakiś sposób, aby uzyskać końcową instrukcję SQL jako ciąg znaków, który powinien wyglądać tak:

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

Jeśli ktoś zastanawia się, dlaczego miałbym to zrobić:

  • do rejestrowania (nieudanych) instrukcji
  • za możliwość skopiowania i wklejenia go do Enterprise Manager w celach testowych
manekin
źródło
1
Dlaczego zaznaczyłeś odpowiedź stackoverflow.com/a/265261/206730, jeśli nie rozróżniasz różnych typów danych, Wstrzykiwania SQL, podobnych nazw parametrów (zastąp problem) ...?
Kiquenet
@Kiquenet Mógłbym przysiąc, że spróbowałem, ale mi to nie pozwoliło. Teraz działa. Dziękuję Ci za to.
manekin
Jeśli chcesz dokładnie wygenerować SQL, który byłby uruchomiony, spójrz na TdsParser.TdsExecuteRPC ( github.com/Microsoft/referencesource/blob/master/System.Data/... ) i trochę się bój.
Rory,

Odpowiedzi:

110

Chociaż nie jest idealny, oto coś, co podrzuciłem dla TSQL - można go łatwo dostosować do innych smaków ... Jeśli nic innego nie da ci punktu wyjścia do własnych ulepszeń :)

Wykonuje to zadanie OK dotyczące typów danych i parametrów wyjściowych itp., Podobnie jak w przypadku „wykonywania procedury składowanej” w SSMS. Najczęściej używaliśmy SP, więc polecenie „tekst” nie uwzględnia parametrów itp

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

to generuje dane wyjściowe według tych linii ...

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);
Podlotek
źródło
7
Dobra robota, próbująca rozwiązać problem tutaj, głosowana za sam wysiłek.
Adam Tolley,
3
Jaka byłaby Twoja metoda „ToBooleanOrDefault (false)”?
Benoittr
6
@Benoittr, możesz zobaczyć wdrożenie ToBooleanOrDefaulttutaj: Pytanie # 3244850
Alexandre Marcondes
@flapper co z polem typu blob lub tablicą bajtów
Smith
1
Dokonano drobnych korekt i dodano parametry wartości tabeli. Wszystko działa na GitHub i pakiecie .Net Standard 2.0 Nuget github.com/jphellemons/CommandAsSql Dziękuję Flapper! Czy mogę dodać Cię jako współpracownika?
JP Hellemons,
128

Do celów logowania obawiam się, że nie ma lepszego sposobu na zrobienie tego niż samodzielne skonstruowanie ciągu:

string query = cmd.CommandText;

foreach (SqlParameter p in cmd.Parameters)
{
    query = query.Replace(p.ParameterName, p.Value.ToString());
}
Kon
źródło
Jeśli to zrobię, będę musiał rozróżnić różne typy danych. Następnie mógłbym pominąć sparametryzowane zapytanie i wykonać to.
manekin
2
manekin: niezupełnie. jeśli wykonasz przygotowane polecenie, jesteś narażony na atak iniekcji SQL. +1 za odpowiedź.
Sunny Milenov,
11
Tu jest gotcha. Jeśli mam parametry „Param” i „differentParam” jako parametry, to czyni innyParam bezużytecznym, ponieważ zastępuje go „ValueParam”. zakładając Param = wartość.
Alok
5
Pytanie nie dotyczy technik kodowania defensywnego, dlatego też zerowe kontrole referencyjne nie są częścią odpowiedzi. Sugeruje się, że należy go wdrożyć, dlatego nie uważam tego za konstruktywny komentarz.
Kon
2
nieco lepszym podejściem do wyeliminowania problemu z podobnymi nazwami parametrów wskazanymi przez @Alok może być użycie do query = Regex.Replace(query, @"\b" + p.ParameterName + @"\b", p.Value.ToString());zastąpienia parametrów w ciągu. Zastąpi to „całe słowo”. Może to nie być uniwersalne rozwiązanie, ponieważ \ b oznacza pozycję między znakiem słowa a znakiem innym niż słowo, więc w przypadku gdy nazwy parametrów zaczynają się od @, należy użyć p.ParameterName + @"\b"parametru zastępującego parametr w ciągu zapytania.
stambikk
47

Nie możesz, ponieważ nie generuje żadnego SQL.

Sparametryzowane zapytanie (jedno w CommandText) jest wysyłane do SQL Server jako odpowiednik przygotowanej instrukcji. Po wykonaniu polecenia parametry i tekst zapytania są traktowane osobno. W żadnym momencie nie jest generowany pełny ciąg SQL.

Za pomocą programu SQL Profiler można zajrzeć za kulisy.

Tomalak
źródło
6
SQL jest generowany - spójrz w Profiler - to jest tekst, który chciałbym mieć do celów logowania
kpkpkp
oprócz SQL Profilera (który jest przestarzały dla nowszego SQL Servera, jeśli poprawnie zrozumiałem komentarz MS) może również używać Monitora aktywności zgodnie z inną odpowiedzią tutaj
George Birbilis
27

Potrzebowałem polecenia podobnego do transformatora łańcuchowego, aby umożliwić bardziej szczegółowe rejestrowanie, więc napisałem ten. Wygeneruje tekst potrzebny do ponownego wykonania polecenia w nowej sesji, w tym parametry wyjściowe i parametry strukturalne. Jest to lekko przetestowane, ale zastrzeżenie jest puste.

Przykład:

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;

Będzie produkować:

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

Realizacja:

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
źródło
Dzięki za to, jest dość kompleksowe! :-)
Alastair Maw
Dokładnie to, czego szukałem, dzięki.
Xilmiki,
Użyłem tego jako punktu wyjścia dla wersji, która używała sp_executesql do obsługi parametrów w pojedynczej instrukcji, zamiast deklarować zmienne oddzielnie. Ten kod naprawdę zadbał o wszystkie żmudne prace i musiałem tylko zmienić kolejność elementów. Wielkie dzięki!
pettys
1
Czy nie wymaga to przedrostka „N” dla literałów ciągów SQL? W przeciwnym razie możesz uzyskać wiele „?”. Bezgłośnie. Zły. (Przynajmniej z SQL Server 2005 - nie sprawdziłem z mniej starymi wersjami.)
Paul Groke,
@PaulGroke, dobry połów. Zaktualizowałem, aby uwzględnić Nprefiks.
Mitch,
6

Miałem również ten problem, w którym niektóre sparametryzowane zapytania lub sps dawałyby mi SqlException (głównie łańcuch lub dane binarne zostałyby obcięte), a instrukcje były trudne do debugowania (O ile wiem, obecnie nie ma obsługi SQL-profilera dla SQL Azure)

W reakcjach widzę dużo kodu simularnego. Skończyłem, umieszczając moje rozwiązanie w projekcie Sql-Library do wykorzystania w przyszłości.

Generator jest dostępny tutaj: https://github.com/jeroenpot/SqlHelper/blob/master/Source/Mirabeau.MsSql.Library/SqlGenerator.cs

Obsługuje zarówno CommandType.Text, jak i CommandType.StoredProcedure

A jeśli zainstalujesz pakiet nuget , możesz go wygenerować za pomocą tego oświadczenia:

SqlDebugHelper.CreateExecutableSqlStatement(sql, parameters);
Jeroen Pot
źródło
Nieźle, przynajmniej wyświetla wartości dla każdego parametru, ale nadal nie wypełnia wartości. Przynajmniej mogę to zrobić za pomocą notatnika, dzięki!
Harvey Lin
5

Jeśli używasz programu SQL Server, możesz użyć programu SQL Server Profiler (jeśli go masz), aby wyświetlić faktycznie wykonywany ciąg poleceń. Przydałoby się to do celów testowania kopiowania / wklejania, ale obawiam się, że nie do logowania.

Rockcoder
źródło
3

Późna odpowiedź, wiem, ale ja też tego chciałem, aby móc zalogować SQL. Poniższe informacje są krótkie i spełniają moje potrzeby.

Poniżej przedstawiono kod SQL, który można skopiować / wkleić w SSMS (odpowiednio zastępuje parametry wartościami). Możesz dodać więcej typów, ale spełnia to wszystko, czego używam w tym przypadku.

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

Teraz mogę zalogować SQL bezpośrednio przed jego wykonaniem:

LogSQL(queryCmd)
queryCmd.ExecuteNonQuery()
Paul Sturm
źródło
2

Profiler jest najlepszą opcją.

Może być konieczne skopiowanie zestawu instrukcji z profilera ze względu na związane z tym kroki przygotowania + wykonania.

Ed Guiness
źródło
2

Miałem to samo dokładne pytanie i po przeczytaniu tych odpowiedzi błędnie zdecydowałem, że nie można uzyskać dokładnego wynikowego zapytania. Myliłem się.

Rozwiązanie: Otwórz Activity Monitorw SQL Server Management Studio, zawęź sekcję procesów do nazwy użytkownika logowania, bazy danych lub nazwy aplikacji używanej przez aplikację w ciągu połączenia. Po nawiązaniu połączenia z odświeżaniem db Activity Monitor. Gdy zobaczysz proces, kliknij go prawym przyciskiem myszy i View Details.

Uwaga: może to nie być opłacalną opcją dla zajętej bazy danych. Ale powinieneś być w stanie znacznie zawęzić wynik za pomocą tych kroków.

alan
źródło
2

Używane części kodu klapa jest dla mojego roztworu, który powraca cały ciąg SQL w tym wartości parametrów, aby uruchomić w 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;
    }
Barry-Dean
źródło
Twoje „rozwiązanie” nie działa. Zastąpiłeś \ r i \ n „”, kiedy powinieneś był użyć „”. Co więcej, nie działa, jeśli masz więcej niż 9 parametrów, ponieważ zastąpienie „@ p1” zastępuje zarówno „@ p1”, jak i „@ p10” wszelkiego rodzaju szalonymi wynikami. Skopiowanie listy parametrów i cofnięcie tego było szybką naprawą tego, co robię.
BH
Ponadto kod nie będzie działał w przypadku polecenia aktualizacji z powodu zamiany „null”.
BH
w rzeczy samej kod Flappera nie obsługuje DBNull, jest tutaj problem z biblioteką CommandAsSQL, która jest na nim oparta: github.com/jphellemons/CommandAsSql/issues/1
George Birbilis 19.09.18
2

Moje rozwiązanie:

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
źródło
2

Napisałem tę metodę dla siebie. Używam części kodu Bruno Ratnieksa . Może komuś się przyda.

 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
źródło
1

Jeśli ma to tylko sprawdzić, jak parametr jest sformatowany w zapytaniu wynikowym, większość DBMS pozwoli na zapytanie literałów z niczego. A zatem:

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

W ten sposób możesz sprawdzić, czy cytaty są podwojone itp.

MPelletier
źródło
1

Oto, czego używam do wyprowadzania list parametrów procedury składowanej do konsoli debugowania:

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

Spowoduje to wygenerowanie wyprowadzenia konsoli dla tego:

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

Umieszczam ten kod bezpośrednio pod każdą procedurą, którą chcę debugować i jest podobny do sesji profilera SQL, ale w języku C #.

brak kariery
źródło
1

Zmodyfikowana wersja odpowiedzi Kona, ponieważ tylko częściowo działa z podobnymi nazwanymi parametrami. Wadą korzystania z funkcji zamiany ciągu. Poza tym w pełni przypisuję mu rozwiązanie.

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;
}
HouseCat
źródło
0

To rozwiązanie działa teraz dla mnie. Może jest to przydatne dla kogoś. Proszę wybaczyć nadmiarowość.

    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
manekin
źródło
0

Jak wspomniano @pkExec i @Alok, funkcja Zastąp nie działa w 100% przypadków. Jest to rozwiązanie, którego użyłem w naszym DAL, który używa RegExp tylko do „dopasowania całego słowa” i prawidłowego formatowania typów danych. W ten sposób wygenerowany SQL można przetestować bezpośrednio w MySQL Workbench (lub SQLSMS itp.) :)

(Zamień funkcję MySQLHelper.EscapeString () zgodnie z zastosowanym 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

Przykład:

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

Zostanie wygenerowany:

SELECT * FROM order WHERE order_status = 'C' AND order_date = '2015-01-01 00:00:00'
JotaSantana
źródło
0

zapytania komend sql będą wykonywane przy pomocy exec sp_executesql, więc oto inny sposób na uzyskanie instrukcji jako ciągu (metoda rozszerzenia 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;
}
o_link
źródło
0

musiałem również obejmować procedury niezapisane, więc rozszerzyłem bibliotekę CommandAsSql (patrz komentarze pod odpowiedzią @ Flapper powyżej) za pomocą tej logiki:

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

żądanie ściągnięcia znajduje się pod adresem : https://github.com/jphellemons/CommandAsSql/pull/3/commits/527d696dc6055c5bcf858b9700b83dc863f04896

pomysł Regex opierał się na komentarzach @ stambikk i EvZ powyżej oraz sekcji „Aktualizacja:” https://stackoverflow.com/a/2544661/903783, w której wspomniano o „negatywnym stwierdzeniu wstecznym”. Użycie \ B zamiast \ b do wykrywania granicy słowa na początku wyrażenia regularnego jest spowodowane tym, że p.parameterName zawsze zaczyna się od „@”, który nie jest znakiem słownym.

należy zauważyć, że ParameterValueForSQL () to metoda rozszerzenia zdefiniowana w bibliotece CommandAsSql do obsługi problemów takich jak wartości parametrów ciągu pojedynczego cudzysłowu itp.

George Birbilis
źródło
btw, inny obiecujący fragment kodu jest na github.com/jeroenpot/SqlHelper/blob/master/Source/… (wspomniany w odpowiedzi w tym wątku). Prawdopodobnie może scalić kod z SQLCommand i SqlGenerator, jeśli znajdziesz coś, co nie działa w jednym lub drugim
George Birbilis
... w ostatnim komentarzu miał na myśli bibliotekę CommandAsSQL zamiast SQLCommand
George Birbilis
0

Jeśli przekonwertujesz tekst polecenia:

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

Teraz możesz uzyskać tekst parametru innego niż parametr w następujący sposób:

    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)

a wynikiem jest „UPDATE someTable SET Value = 'myValue' WHERE Id = 1234” bez parametru

użytkownik11982798
źródło
0

Rozszerzony kod Kon, aby pomóc w debugowaniu procedury składowanej:

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

W moim pierwszym przypadku testowym wygenerował:

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

Prawdopodobnie będziesz musiał dodać kilka warunkowych przypisań typu „..jest ...”, np. Dla dat i godzin.

CAK2
źródło
-1

Jedna wkładka:

string.Join(",", from SqlParameter p in cmd.Parameters select p.ToString()) 
CheesusCrust
źródło
-1

Od polecenia parametru do polecenia innego niż parametr można zmienić to

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

Do

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
użytkownik11982798
źródło