Comment passer des paramètres à la méthode DbContext.Database.ExecuteSqlCommand?


222

Supposons simplement que j'ai un besoin valide d'exécuter directement une commande sql dans Entity Framework. J'ai du mal à comprendre comment utiliser les paramètres dans ma déclaration SQL. L'exemple suivant (pas mon vrai exemple) ne fonctionne pas.

var firstName = "John";
var id = 12;
var sql = @"Update [User] SET FirstName = @FirstName WHERE Id = @Id";
ctx.Database.ExecuteSqlCommand(sql, firstName, id);

La méthode ExecuteSqlCommand ne vous permet pas de passer des paramètres nommés comme dans ADO.Net et la documentation de cette méthode ne donne aucun exemple sur la façon d'exécuter une requête paramétrée.

Comment spécifier correctement les paramètres?

Réponses:


294

Essaye ça:

var sql = @"Update [User] SET FirstName = @FirstName WHERE Id = @Id";

ctx.Database.ExecuteSqlCommand(
    sql,
    new SqlParameter("@FirstName", firstname),
    new SqlParameter("@Id", id));

2
Cela devrait vraiment être la bonne réponse, celle ci-dessus est sujette aux attaques et ne correspond pas aux meilleures pratiques.
min

8
@Min, la réponse acceptée n'est pas plus sujette aux attaques que cette réponse. Peut-être que vous pensiez qu'il utilisait string.Format - ce n'est pas le cas.
Simon MᶜKenzie

1
Cela devrait être marqué comme réponse! C'est la seule et unique bonne réponse car elle fonctionne avec DateTime.
Sven

219

Il s'avère que cela fonctionne.

var firstName = "John";
var id = 12;
var sql = "Update [User] SET FirstName = {0} WHERE Id = {1}";
ctx.Database.ExecuteSqlCommand(sql, firstName, id);

12
Cela fonctionnera, mais ce mécanisme permet l'injection SQL et empêche également la base de données de réutiliser un plan d'exécution lorsque l'instruction réapparaît mais avec des valeurs différentes.
Greg Biles

95
@GregB Je ne pense pas que vous ayez raison ici. J'ai testé qu'il ne me permettrait pas, par exemple, de terminer un littéral de chaîne plus tôt. De plus, j'ai regardé le code source et j'ai constaté qu'il utilise DbCommand.CreateParameter pour encapsuler toutes les valeurs brutes dans les paramètres. Donc pas d'injection SQL et une belle invocation succincte de méthode.
Josh Gallagher

7
@JoshGallagher Oui, vous avez raison. Je pensais à une chaîne. Scénario de formatage assemblant cela.
Greg Biles

6
Il ne fonctionne PAS à partir de SQL Server 2008 R2. Vous aurez @ p0, @ p2, ..., @pN au lieu des paramètres que vous avez passés. Utilisez SqlParameter("@paramName", value)plutôt.
Arnthor

Je ne peux pas croire que personne n'ait mentionné l'impact sur les performances de cette requête si les colonnes de la table de base de données sont spécifiées en tant que varchar ANSI. Les chaînes .Net sont unicode signifie que les paramètres seront transmis au serveur en tant que nvarchar. Cela entraînerait un problème de performances important car la couche de données doit effectuer la traduction des données. Vous devez vous en tenir à l'approche de SqlParameter et spécifier les types de données.
akd

68

Tu peux soit:

1) Transmettez les arguments bruts et utilisez la syntaxe {0}. Par exemple:

DbContext.Database.SqlQuery("StoredProcedureName {0}", paramName);

2) Transmettez les arguments de la sous-classe DbParameter et utilisez la syntaxe @ParamName.

DbContext.Database.SqlQuery("StoredProcedureName @ParamName", 
                                   new SqlParameter("@ParamName", paramValue);

Si vous utilisez la première syntaxe, EF encapsulera vos arguments avec les classes DbParamater, leur attribuera des noms et remplacera {0} par le nom du paramètre généré.

La première syntaxe est préférée car vous n'avez pas besoin d'utiliser une fabrique ou de savoir quel type de DbParamaters créer (SqlParameter, OracleParamter, etc.).


6
Voté pour avoir mentionné que la syntaxe {0} est indépendante de la base de données. "... vous n'avez [sic] pas besoin d'utiliser une usine ou de savoir quel type de DbParamaters [sic] créer ..."
Makotosan

Le scénario 1 est déconseillé au profit d'une version interpolée. L'équivalent est maintenant: DbContext.Database.ExecuteSqlInterpolated ($ "StoredProcedureName {paramName}");
ScottB

20

Les autres réponses ne fonctionnent pas lors de l'utilisation d'Oracle. Vous devez utiliser :au lieu de @.

var sql = "Update [User] SET FirstName = :FirstName WHERE Id = :Id";

context.Database.ExecuteSqlCommand(
   sql,
   new OracleParameter(":FirstName", firstName), 
   new OracleParameter(":Id", id));

Dieu merci, personne n'utilise Oracle. Enfin pas volontairement! EDIT: Toutes mes excuses pour la blague tardive! EDIT: Toutes mes excuses pour la mauvaise blague!
Chris Bordeman

18

Essayez ceci (édité):

ctx.Database.ExecuteSqlCommand(sql, new SqlParameter("FirstName", firstName), 
                                    new SqlParameter("Id", id));

L'idée précédente était fausse.


Lorsque je fais cela, j'obtiens l'erreur suivante: "Aucun mappage n'existe du type d'objet System.Data.Objects.ObjectParameter à un type natif de fournisseur géré connu."
jessegavin

Désolé mon erreur. Utilisez DbParameter.
Ladislav Mrnka

7
DbParameter est abstrait. Vous devrez utiliser SqlParameter ou utiliser un DbFactory pour créer un DbParameter.
jrummell

12

Pour l'entité Framework Core 2.0 ou supérieure, la bonne façon de procéder est la suivante:

var firstName = "John";
var id = 12;
ctx.Database.ExecuteSqlCommand($"Update [User] SET FirstName = {firstName} WHERE Id = {id}";

Notez qu'Entity Framework produira les deux paramètres pour vous, vous êtes donc protégé contre l'injection SQL.

Notez également qu'il ne s'agit PAS:

var firstName = "John";
var id = 12;
var sql = $"Update [User] SET FirstName = {firstName} WHERE Id = {id}";
ctx.Database.ExecuteSqlCommand(sql);

car cela ne vous protège PAS contre l'injection SQL et aucun paramètre n'est produit.

Voir ceci pour plus.


3
J'adore tellement .NET Core 2.0 qu'il me donne des larmes de joie: ')
Joshua Kemmerer

Je peux voir l'enthousiasme de certaines personnes depuis .NET Core 2.0 a été une conduite plus douce pour moi jusqu'à présent.
Paul Carlton

Comment le premier n'est-il pas vulnérable à l'injection SQL? Les deux utilisent l'interpolation de chaînes C #. Le premier ne serait pas en mesure de préempter l'expansion de chaîne, pour autant que je sache. Je soupçonne que vous vouliez que cela soitctx.Database.ExecuteSqlCommand("Update [User] SET FirstName = {firstName} WHERE Id = {id}", firstName, id);
CodeNaked

@CodeNaked, non, je ne voulais pas dire ça. EF est conscient du problème et crée deux paramètres réels pour vous protéger. Ce n'est donc pas seulement une chaîne qui est transmise. Voir le lien ci-dessus pour plus de détails. Si vous l'essayez dans VS, ma version n'émettra pas d'avertissement concernant Sql Injection, et l'autre le fera.
Greg Gum

1
@GregGum - TIL about FormattableString. Tu as raison et c'est plutôt cool!
CodeNaked

4

Version simplifiée pour Oracle. Si vous ne souhaitez pas créer OracleParameter

var sql = "Update [User] SET FirstName = :p0 WHERE Id = :p1";
context.Database.ExecuteSqlCommand(sql, firstName, id);

2
Dans SQL Server, j'utilise @ p0 au lieu de: p0.
Marek Malczewski

3
var firstName = "John";
var id = 12;

ctx.Database.ExecuteSqlCommand(@"Update [User] SET FirstName = {0} WHERE Id = {1}"
, new object[]{ firstName, id });

C'est tellement simple !!!

Image pour connaître la référence des paramètres

entrez la description de l'image ici


2

Pour la méthode async ("ExecuteSqlCommandAsync"), vous pouvez l'utiliser comme ceci:

var sql = @"Update [User] SET FirstName = @FirstName WHERE Id = @Id";

await ctx.Database.ExecuteSqlCommandAsync(
    sql,
    parameters: new[]{
        new SqlParameter("@FirstName", firstname),
        new SqlParameter("@Id", id)
    });

Ce n'est pas agnostique db, cela ne fonctionnera que pour MS-SQL Server. Il échouera pour Oracle ou PG.
ANeves

1

Si vos types de données de base de données sous-jacents sont varchar, vous devez vous en tenir à l'approche ci-dessous. Sinon, la requête aurait un impact énorme sur les performances.

var firstName = new SqlParameter("@firstName", System.Data.SqlDbType.VarChar, 20)
                            {
                                Value = "whatever"
                            };

var id = new SqlParameter("@id", System.Data.SqlDbType.Int)
                            {
                                Value = 1
                            };
ctx.Database.ExecuteSqlCommand(@"Update [User] SET FirstName = @firstName WHERE Id = @id"
                               , firstName, id);

Vous pouvez vérifier le profileur SQL pour voir la différence.


0
public static class DbEx {
    public static IEnumerable<T> SqlQueryPrm<T>(this System.Data.Entity.Database database, string sql, object parameters) {
        using (var tmp_cmd = database.Connection.CreateCommand()) {
            var dict = ToDictionary(parameters);
            int i = 0;
            var arr = new object[dict.Count];
            foreach (var one_kvp in dict) {
                var param = tmp_cmd.CreateParameter();
                param.ParameterName = one_kvp.Key;
                if (one_kvp.Value == null) {
                    param.Value = DBNull.Value;
                } else {
                    param.Value = one_kvp.Value;
                }
                arr[i] = param;
                i++;
            }
            return database.SqlQuery<T>(sql, arr);
        }
    }
    private static IDictionary<string, object> ToDictionary(object data) {
        var attr = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance;
        var dict = new Dictionary<string, object>();
        foreach (var property in data.GetType().GetProperties(attr)) {
            if (property.CanRead) {
                dict.Add(property.Name, property.GetValue(data, null));
            }
        }
        return dict;
    }
}

Usage:

var names = db.Database.SqlQueryPrm<string>("select name from position_category where id_key=@id_key", new { id_key = "mgr" }).ToList();

7
Y a-t-il une chance que vous puissiez expliquer ce code et pourquoi c'est une réponse à la question posée, pour que ceux qui viennent le trouver plus tard puissent le comprendre?
Andrew Barber

1
Problème avec la syntaxe {0} qui vous fait perdre la lisibilité - personnellement, je ne l'aime pas vraiment. Problème de passage de SqlParameters dont vous avez besoin pour spécifier l'implémentation concrète de DbParameter (SqlParameter, OracleParameter, etc.). L'exemple fourni vous permet d'éviter ces problèmes.
Neco

3
Je suppose que c'est une opinion valable, mais vous n'avez pas répondu à la question qui se pose ici; Comment passer des paramètres à ExecuteSqlCommand()Vous devez être sûr de répondre à la question spécifique posée lorsque vous publiez des réponses.
Andrew Barber

3
Voté parce qu'il est inutilement compliqué (p. Ex. Utilise la réflexion, réinvente la roue, ne tient pas compte des différents fournisseurs de bases de données)
un phu

Voté parce qu'il montre l'une des solutions possibles. Je suis sûr que ExecuteSqlCommand accepte les paramètres de la même manière que SqlQuery. J'aime aussi le style Dapper.net de passer les paramètres.
Zar Shardan

0

Plusieurs paramètres dans une procédure stockée qui a plusieurs paramètres dans vb:

Dim result= db.Database.ExecuteSqlCommand("StoredProcedureName @a,@b,@c,@d,@e", a, b, c, d, e)

0

Les procédures stockées peuvent être exécutées comme ci-dessous

 string cmd = Constants.StoredProcs.usp_AddRoles.ToString() + " @userId, @roleIdList";
                        int result = db.Database
                                       .ExecuteSqlCommand
                                       (
                                          cmd,
                                           new SqlParameter("@userId", userId),
                                           new SqlParameter("@roleIdList", roleId)
                                       );

N'oubliez pas de faire en utilisant System.Data.SqlClient
FlyingV

0

Pour .NET Core 2.2, vous pouvez utiliser FormattableStringpour SQL dynamique.

//Assuming this is your dynamic value and this not coming from user input
var tableName = "LogTable"; 
// let's say target date is coming from user input
var targetDate = DateTime.Now.Date.AddDays(-30);
var param = new SqlParameter("@targetDate", targetDate);  
var sql = string.Format("Delete From {0} Where CreatedDate < @targetDate", tableName);
var froamttedSql = FormattableStringFactory.Create(sql, param);
_db.Database.ExecuteSqlCommand(froamttedSql);

N'oubliez pas de faire en utilisant System.Data.SqlClient
FlyingV
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.