Obtenir l'instruction SQL générée à partir d'un objet SqlCommand?

186

J'ai le code suivant:

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

Je me demande s'il existe un moyen d'obtenir la déclaration SQL finale sous forme de chaîne, qui devrait ressembler à ceci:

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

Si quelqu'un se demande pourquoi je ferais cela:

  • pour la journalisation des instructions (échouées)
  • pour avoir la possibilité de le copier-coller dans l'Enterprise Manager à des fins de test
mannequin
la source
1
Pourquoi avez-vous marqué la réponse stackoverflow.com/a/265261/206730 sinon faire la distinction entre les différents types de données, Sql Injection, les noms de paramètres similaires (problème de remplacement) ...?
Kiquenet
@Kiquenet J'aurais pu jurer que j'ai essayé ça mais ça ne m'a pas laissé faire. Maintenant ça marche. Merci pour ça.
mannequin
Si vous voulez générer avec précision le SQL qui serait exécuté, jetez un œil à TdsParser.TdsExecuteRPC ( github.com/Microsoft/referencesource/blob/master/System.Data/… ) et ayez un peu peur.
Rory

Réponses:

110

Bien que pas parfait, voici quelque chose que j'ai créé pour TSQL - qui pourrait être facilement modifié pour d'autres saveurs ... Si rien d'autre, cela vous donnera un point de départ pour vos propres améliorations :)

Cela fait un travail OK sur les types de données et les paramètres de sortie, etc. similaire à l'utilisation de "exécuter la procédure stockée" dans SSMS. Nous avons principalement utilisé des SP, donc la commande "text" ne tient pas compte des paramètres, etc.

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

cela génère une sortie le long de ces lignes ...

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
la source
7
Beau travail en fait d'essayer de s'attaquer au problème ici, voté pour l'effort seul.
Adam Tolley
3
Quelle serait votre méthode "ToBooleanOrDefault (false)"?
Benoittr
6
@Benoittr, vous pouvez voir une implémentation de ToBooleanOrDefaultici: Question # 3244850
Alexandre Marcondes
@flapper que dire d'un champ blob ou d'un tableau d'octets
Smith
1
Quelques ajustements mineurs et ajout de paramètres de valeur de table. Tout est en place sur GitHub et un package Nuget .Net Standard 2.0 github.com/jphellemons/CommandAsSql Merci Flapper! Puis-je vous ajouter comme collaborateur?
JP Hellemons
128

À des fins de journalisation, j'ai bien peur qu'il n'y ait pas de meilleur moyen de faire cela que de construire la chaîne vous-même:

string query = cmd.CommandText;

foreach (SqlParameter p in cmd.Parameters)
{
    query = query.Replace(p.ParameterName, p.Value.ToString());
}
Kon
la source
Si je fais cela, je devrai faire la distinction entre les différents types de données. Ensuite, je pourrais ignorer la requête paramétrée et l'exécuter.
mannequin
2
mannequin: pas vraiment. si vous exécutez une instruction préparée, vous risquez une attaque par injection SQL. +1 pour la réponse.
Sunny Milenov
11
Theres a gotcha ici. Si j'ai "Param" et "differentParam" comme paramètres, cela rend le differentParam inutile car il le remplace par "ValueParam". en supposant Param = Valeur.
Alok
5
La question ne traite pas des techniques de codage défensives, donc les vérifications de références nulles ne font pas partie de la réponse. Le fait qu'il devrait être implémenté est implicite, donc je ne vois pas cela comme un commentaire constructif.
Kon
2
une approche légèrement meilleure pour éliminer le problème avec des noms de paramètres similaires signalés par @Alok pourrait être d'utiliser query = Regex.Replace(query, @"\b" + p.ParameterName + @"\b", p.Value.ToString());pour remplacer les paramètres dans la chaîne. Cela remplacera le «mot entier». Ce n'est peut-être pas une solution universelle car le \ b marque une position entre un caractère de mot et un caractère non-mot, donc dans le cas où les noms de vos paramètres commencent par @, vous devriez utiliser p.ParameterName + @"\b"pour remplacer le paramètre dans la chaîne de requête.
stambikk
47

Vous ne pouvez pas, car il ne génère aucun SQL.

La requête paramétrée (celle dans CommandText) est envoyée au serveur SQL comme l'équivalent d'une instruction préparée. Lorsque vous exécutez la commande, les paramètres et le texte de la requête sont traités séparément. A aucun moment, une chaîne SQL complète n'est générée.

Vous pouvez utiliser SQL Profiler pour jeter un œil dans les coulisses.

Tomalak
la source
6
SQL est généré - regardez dans Profiler - c'est le texte que j'aimerais avoir à des fins de journalisation
kpkpkp
à part SQL Profiler (qui est obsolète pour les nouveaux SQL Server si j'ai bien compris certains commentaires MS) peut également utiliser Activity Monitor selon une autre réponse ici
George Birbilis
27

J'avais besoin d'une commande similaire à string transformer pour permettre une journalisation plus détaillée, j'ai donc écrit celle-ci. Il produira le texte nécessaire pour réexécuter la commande dans une nouvelle session, y compris les paramètres de sortie et les paramètres structurés. Il est légèrement testé, mais attention au vide.

Exemple:

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;

Produira:

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

La mise en oeuvre:

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
la source
Merci pour cela, c'est assez complet! :-)
Alastair Maw
Exactement ce que je cherchais, merci.
Xilmiki
Je l'ai utilisé comme point de départ pour une version de celui-ci qui utilisait sp_executesql pour gérer les paramètres dans une seule instruction plutôt que de déclarer les variables séparément. Ce code a vraiment pris en charge tout le travail fastidieux et j'ai juste dû réorganiser les pièces. Grand merci!
pettys
1
Cela ne nécessite-t-il pas un préfixe "N" pour les littéraux de chaîne SQL? Sinon, vous pourriez obtenir plusieurs "?" S. Silencieusement. Mauvais. (Au moins avec SQL Server 2005 - je n'ai pas vérifié avec des versions moins anciennes.)
Paul Groke
@PaulGroke, bonne prise. J'ai mis à jour pour inclure le Npréfixe.
Mitch
6

J'ai également eu ce problème où certaines requêtes paramétrées ou sp me donneraient une SqlException (la plupart du temps la chaîne ou les données binaires seraient tronquées), et les déclarations étaient difficiles à déboguer (pour autant que je sache, il n'y a actuellement pas de support de sql-profiler pour SQL Azure)

Je vois beaucoup de code simulaire dans les réactions ici. J'ai fini par mettre ma solution dans un projet Sql-Library pour une utilisation future.

Le générateur est disponible ici: https://github.com/jeroenpot/SqlHelper/blob/master/Source/Mirabeau.MsSql.Library/SqlGenerator.cs

Il prend en charge à la fois CommandType.Text et CommandType.StoredProcedure

Et si vous installez le package nuget, vous pouvez le générer avec cette instruction:

SqlDebugHelper.CreateExecutableSqlStatement(sql, parameters);
Pot Jeroen
la source
Pas trop mal, il répertorie au moins les valeurs de chaque paramètre, mais ne remplit toujours pas les valeurs. Au moins, je peux utiliser le bloc-notes pour le faire moi-même, merci!
Harvey Lin
5

Si vous utilisez SQL Server, vous pouvez utiliser SQL Server Profiler (si vous l'avez) pour afficher la chaîne de commande qui est réellement exécutée. Cela serait utile pour les tests de copier / coller, mais pas pour la journalisation, j'en ai peur.

Rockcoder
la source
3

Réponse tardive, je sais mais je voulais aussi cela pour pouvoir enregistrer le SQL. Ce qui suit est court et répond à mes besoins.

Ce qui suit produit du SQL que vous pouvez copier / coller dans SSMS (il remplace correctement les paramètres par les valeurs). Vous pouvez ajouter plus de types, mais cela répond à tout ce que j'utilise dans ce cas.

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

Maintenant, je peux enregistrer le SQL juste avant de l'exécuter:

LogSQL(queryCmd)
queryCmd.ExecuteNonQuery()
Paul Sturm
la source
2

Profiler est sans conteste votre meilleure option.

Vous devrez peut-être copier un ensemble d'instructions à partir du profileur en raison des étapes de préparation + exécution impliquées.

Ed Guiness
la source
2

J'ai eu la même question exacte et après avoir lu ces réponses, j'ai décidé par erreur qu'il n'était pas possible d'obtenir la requête résultante exacte. J'avais tort.

Solution: Ouvrir Activity Monitordans SQL Server Management Studio, rétrécir la section processus au nom d' utilisateur de connexion, base de données ou le nom de l' application que votre application utilise dans la chaîne de connexion. Lorsque l'appel est fait à la db refresh Activity Monitor. Lorsque vous voyez le processus, faites un clic droit dessus etView Details .

Notez que cela peut ne pas être une option viable pour une base de données occupée. Mais vous devriez pouvoir réduire considérablement le résultat en suivant ces étapes.

Alan
la source
2

Utilisé une partie du code de Flapper pour ma solution, qui retourne toute la chaîne SQL, y compris les valeurs de paramètres à exécuter dans 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
la source
Votre «solution» ne fonctionne pas. Vous avez remplacé \ r et \ n par "" alors que vous auriez dû utiliser "". De plus, cela ne fonctionne pas si vous avez plus de 9 paramètres car le remplacement de «@ p1» remplace à la fois «@ p1» et «@ p10» par toutes sortes de résultats fous. Copier la liste des paramètres et l'inverser était une solution rapide pour ce que je fais.
BH
De plus, votre code ne fonctionnera pas pour une commande de mise à jour en raison du remplacement «est nul».
BH
en effet le code de Flapper ne gère pas DBNull, il y a un problème ici pour la bibliothèque CommandAsSQL qui est basée dessus: github.com/jphellemons/CommandAsSql/issues/1
George Birbilis
2

Ma solution:

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
la source
2

J'ai écrit cette méthode pour moi. J'utilise une partie du code de Bruno Ratnieks . Peut-être que c'est utile à quelqu'un.

 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
la source
1

S'il ne s'agit que de vérifier la mise en forme d'un paramètre dans la requête de résultat, la plupart des SGBD autoriseront l'interrogation de littéraux à partir de rien. Donc:

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

De cette façon, vous pouvez voir si les citations sont doublées, etc.

MPelletier
la source
1

Voici ce que j'utilise pour générer des listes de paramètres pour une procédure stockée dans la console de débogage:

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

Cela générera une sortie de console similaire à ceci:

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

Je place ce code directement sous toute procédure que je souhaite déboguer et est similaire à une session de profileur SQL mais en C #.

porteur
la source
1

Version modifiée de la réponse de Kon car elle ne fonctionne que partiellement avec des paramètres nommés similaires. L'inconvénient de l'utilisation de la fonction String Replace. À part cela, je lui donne tout le crédit sur la solution.

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;
}
Un chat domestique
la source
0

Cette solution fonctionne pour moi en ce moment. Peut-être que c'est utile pour quelqu'un. Veuillez excuser toute redondance.

    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
mannequin
la source
0

Comme @pkExec et @Alok l'ont mentionné, utiliser Remplacer ne fonctionne pas dans 100% des cas. C'est la solution que j'ai utilisée dans notre DAL qui utilise RegExp pour "faire correspondre le mot entier" uniquement et formater correctement les types de données. Ainsi le SQL généré peut être testé directement dans MySQL Workbench (ou SQLSMS, etc ...) :)

(Remplacez la fonction MySQLHelper.EscapeString () en fonction du SGBD utilisé.)

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

Exemple:

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

Sera généré:

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

les requêtes de commande sql seront exécutées avec exec sp_executesql, voici donc une autre façon d'obtenir l'instruction sous forme de chaîne (méthode d'extension 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
la source
0

nécessaire pour couvrir également les procédures non stockées, j'ai donc augmenté la bibliothèque CommandAsSql (voir les commentaires sous la réponse de @ Flapper ci-dessus) avec cette logique:

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

la demande d'extraction est à l' adresse : https://github.com/jphellemons/CommandAsSql/pull/3/commits/527d696dc6055c5bcf858b9700b83dc863f04896

l'idée Regex était basée sur les commentaires de @ stambikk et EvZ ci-dessus et de la section "Mise à jour:" de https://stackoverflow.com/a/2544661/903783 qui mentionne une "assertion négative de regard en arrière". L'utilisation de \ B au lieu de \ b pour la détection de limite de mot au début de l'expression régulière est due au fait que p.parameterName commencera toujours par un "@" qui n'est pas un caractère de mot.

notez que ParameterValueForSQL () est une méthode d'extension définie dans la bibliothèque CommandAsSql pour gérer des problèmes tels que les valeurs de paramètres de chaîne à guillemets simples, etc.

George Birbilis
la source
btw, un autre morceau de code prometteur est à github.com/jeroenpot/SqlHelper/blob/master/Source / ... (mentionné dans une réponse dans ce fil). Peut probablement fusionner le code de SQLCommand et SqlGenerator si vous trouvez quelque chose qui ne fonctionne pas à l'un ou l'autre
George Birbilis
... voulait dire bibliothèque CommandAsSQL au lieu de SQLCommand dans le dernier commentaire
George Birbilis
0

Si vous voulez convertir le texte de commande:

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

Vous pouvez maintenant obtenir le texte de commande sans paramètre comme suit:

    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)

et le résultat est "UPDATE someTable SET Value = 'myValue' WHERE Id = 1234" sans paramètre

user11982798
la source
0

Code de Kon étendu pour aider à déboguer une procédure stockée:

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

Dans mon premier cas de test, il a généré:

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

Vous devrez probablement ajouter d'autres affectations de type "..is ..." conditionnelles, par exemple pour les dates et les heures.

CAK2
la source
-1

Bon mot:

string.Join(",", from SqlParameter p in cmd.Parameters select p.ToString()) 
CheesusCroûte
la source
-1

De la commande de paramètre à la commande sans paramètre, vous pouvez changer celle-ci

Using cmd As SqlCommand = Connection.CreateCommand
    cmd.CommandText = "UPDATE someTable SET Value = @Value"
    cmd.CommandText &= " WHERE Id = @Id"
    cmd.Parameters.AddWithValue("@Id", 1234)
    cmd.Parameters.AddWithValue("@Value", "myValue")
    cmd.ExecuteNonQuery
End Using

À

Private sub Update( byval myID as Int32, byval myVal as String)
    Using cmd As SqlCommand = Connection.CreateCommand
        cmd.CommandText = "UPDATE someTable SET Value = '" & myVaL & "'" & _
                          " WHERE Id = " & myID  
        cmd.ExecuteNonQuery
    End Using
End sub
user11982798
la source