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?

jessegavin
la source

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));
Robert te Kaat
la source
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);
jessegavin
la source
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.).

Will Brown
la source
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));
Ryan M
la source
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.

Ladislav Mrnka
la source
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.

Greg Gum
la source
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);
Andreas
la source
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

zaw
la source
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)
    });
Magu
la source
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.

akd
la source
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();
Neco
la source
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)
Dani
la source
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)
                                       );
Manoj Kumar Bisht
la source
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);
Disjoncteur
la source
N'oubliez pas de faire en utilisant System.Data.SqlClient
FlyingV