Come passare i parametri al metodo DbContext.Database.ExecuteSqlCommand?


222

Supponiamo che io abbia una valida necessità di eseguire direttamente un comando sql in Entity Framework. Ho problemi a capire come usare i parametri nella mia istruzione sql. Il seguente esempio (non il mio esempio reale) non funziona.

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

Il metodo ExecuteSqlCommand non consente di passare parametri denominati come in ADO.Net e la documentazione per questo metodo non fornisce alcun esempio su come eseguire una query con parametri.

Come posso specificare correttamente i parametri?

Risposte:


294

Prova questo:

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

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

2
Questa dovrebbe davvero essere la risposta contrassegnata come corretta, quella sopra è soggetta ad attacchi e non è una buona pratica.
Min

8
@Min, la risposta accettata non è più soggetta agli attacchi di questa risposta. Forse hai pensato che stesse usando string.Format - non lo è.
Simon MᶜKenzie,

1
Questo dovrebbe essere contrassegnato come risposta! Questa è l'unica e l'unica risposta corretta perché funziona con DateTime.
Sven

219

Si scopre che funziona.

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

12
Funzionerà, ma questo meccanismo consente l'inserimento di SQL e impedisce anche al database di riutilizzare un piano di esecuzione quando l'istruzione viene nuovamente visualizzata ma con valori diversi.
Greg Biles,

95
@GregB Non penso che tu abbia ragione qui. Ho verificato che, ad esempio, non mi consentirà di terminare una stringa letteralmente in anticipo. Inoltre, ho esaminato il codice sorgente e ho scoperto che utilizza DbCommand.CreateParameter per racchiudere tutti i valori grezzi in parametri. Quindi nessuna iniezione SQL e una buona invocazione del metodo sintetico.
Josh Gallagher,

7
@JoshGallagher Sì, hai ragione. Stavo pensando a una stringa. Scenario formattato che lo mette insieme.
Greg Biles,

6
NON funziona a partire da SQL Server 2008 R2. Avrai @ p0, @ p2, ..., @pN invece dei parametri che hai passato. Usa SqlParameter("@paramName", value)invece.
Arnthor,

Non posso credere che nessuno abbia menzionato l'impatto sulle prestazioni di questa query se le colonne della tabella del database sono specificate come varchar ANSI. Le stringhe .Net sono unicode significa che i parametri saranno passati al server come nvarchar. Ciò causerebbe un significativo problema di prestazioni poiché il livello dati deve eseguire la traduzione dei dati. È necessario attenersi all'approccio di SqlParameter e specificare i tipi di dati.
akd

68

Puoi:

1) Passa argomenti non elaborati e utilizza la sintassi {0}. Per esempio:

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

2) Passa gli argomenti della sottoclasse DbParameter e usa la sintassi @ParamName.

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

Se si utilizza la prima sintassi, EF avvolgerà effettivamente i propri argomenti con le classi DbParamater, assegnando loro i nomi e sostituendo {0} con il nome del parametro generato.

La prima sintassi, se preferita, perché non è necessario utilizzare una factory o sapere quale tipo di DbParamaters creare (SqlParameter, OracleParamter, ecc.).


6
È stato votato per aver menzionato che la sintassi {0} è indipendente dal database. "... non devi [sic] usare una fabbrica o sapere quale tipo di DbParamaters [sic] per creare ..."
Makotosan

Lo scenario 1 è deprecato a favore di una versione interpolata. L'equivalente ora è: DbContext.Database.ExecuteSqlInterpolated ($ "StoredProcedureName {paramName}");
ScottB,

20

Le altre risposte non funzionano quando si utilizza Oracle. È necessario utilizzare :invece di @.

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

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

Grazie a dio nessuno usa Oracle. Beh, non volontariamente! EDIT: scuse per lo scherzo in ritardo! EDIT: scuse per il brutto scherzo!
Chris Bordeman,

18

Prova questo (modificato):

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

L'idea precedente era sbagliata.


Quando lo faccio ottengo il seguente errore: "Non esiste alcun mapping dal tipo di oggetto System.Data.Objects.ObjectParameter a un tipo nativo noto del provider gestito."
jessegavin,

Scusa, errore mio. Usa DbParameter.
Ladislav Mrnka,

7
DbParameter è astratto. Dovrai usare SqlParameter o utilizzare un DbFactory per creare un DbParameter.
jrummell,

12

Per Entity Framework Core 2.0 o versione successiva, il modo corretto per farlo è:

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

Si noti che Entity Framework produrrà i due parametri per te, quindi sei protetto da Sql Injection.

Si noti inoltre che NON è:

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

perché questo NON ti protegge dall'iniezione SQL e non vengono prodotti parametri.

Vedi questo per di più.


3
Amo .NET Core 2.0 così tanto da farmi lacrime di gioia: ')
Joshua Kemmerer,

Riesco a vedere l'entusiasmo di alcune persone dal momento che .NET Core 2.0 è stato finora un giro più fluido per me.
Paul Carlton,

In che modo il primo non è vulnerabile all'iniezione SQL? Entrambi usano l'interpolazione di stringhe C #. Il primo non sarebbe in grado di impedire l'espansione della stringa, per quanto ne so. Ho il sospetto che volevi dire che fossectx.Database.ExecuteSqlCommand("Update [User] SET FirstName = {firstName} WHERE Id = {id}", firstName, id);
CodeNaked

@CodeNaked, No, non intendevo questo. EF è a conoscenza del problema e crea due parametri effettivi per proteggerti. Quindi non è solo una stringa che viene passata. Vedi link sopra per i dettagli. Se lo provi in ​​VS, la mia versione non emetterà un avviso su SQL Injection, e l'altro lo farà.
Greg Gum,

1
@GregGum - TIL circa FormattableString. Hai ragione ed è fantastico!
CodeNaked

4

Versione semplificata per Oracle. Se non vuoi creare OracleParameter

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

2
In SQL Server uso @ p0 anziché: 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 });

Questo è così semplice !!!

Immagine per conoscere il riferimento ai parametri

inserisci qui la descrizione dell'immagine


2

Per il metodo asincrono ("ExecuteSqlCommandAsync") puoi usarlo in questo modo:

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

Questo non è db-agnostic, funzionerà solo per MS-SQL Server. Non funzionerà per Oracle o PG.
ANeves,

1

Se i tipi di dati del database sottostante sono varchar, è necessario attenersi all'approccio seguente. Altrimenti la query avrebbe un enorme impatto sulle prestazioni.

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

Puoi controllare il profiler SQL per vedere la differenza.


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

Uso:

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

7
Qualche possibilità che tu possa spiegare questo codice e perché è una risposta alla domanda posta, in modo che chi viene a trovarlo in seguito possa capirlo?
Andrew Barber,

1
Problema con la sintassi {0} che perdi la leggibilità - personalmente non mi piace molto. Problema con il passaggio di SqlParameter che è necessario specificare l'implementazione concreta di DbParameter (SqlParameter, OracleParameter ecc.). L'esempio fornito consente di evitare questi problemi.
Neco,

3
Immagino che sia un'opinione valida, ma non hai risposto alla domanda che ti viene effettivamente posta qui; Come passare i parametri a ExecuteSqlCommand()Dovresti essere sicuro di rispondere alla domanda specifica che ti viene posta quando pubblichi le risposte.
Andrew Barber,

3
Sottovalutato perché è inutilmente complicato (ad es. Usa la riflessione, reinventa la ruota, non tiene conto di diversi fornitori di database)
un phu

Votato perché mostra una delle possibili soluzioni. Sono sicuro che ExecuteSqlCommand accetta i parametri allo stesso modo di SqlQuery. Mi piace anche lo stile Dapper.net per passare i parametri.
Zar Shardan,

0

Più parametri in una stored procedure con più parametri in vb:

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

0

Le procedure memorizzate possono essere eseguite come di seguito

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

Non dimenticare di utilizzare System.Data.SqlClient
FlyingV

0

Per .NET Core 2.2, è possibile utilizzare FormattableStringper SQL dinamico.

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

Non dimenticare di utilizzare System.Data.SqlClient
FlyingV
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.