Wie übergebe ich Parameter an die DbContext.Database.ExecuteSqlCommand-Methode?

222

Nehmen wir nur an, ich muss einen SQL-Befehl in Entity Framework direkt ausführen. Ich habe Probleme, herauszufinden, wie Parameter in meiner SQL-Anweisung verwendet werden. Das folgende Beispiel (nicht mein wirkliches Beispiel) funktioniert nicht.

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

Mit der ExecuteSqlCommand-Methode können Sie keine benannten Parameter wie in ADO.Net übergeben, und die Dokumentation für diese Methode enthält keine Beispiele für die Ausführung einer parametrisierten Abfrage.

Wie gebe ich die Parameter richtig an?

Jessegavin
quelle

Antworten:

294

Versuche dies:

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
quelle
2
Dies sollte wirklich die Antwort sein, die als richtig markiert ist. Die obige Antwort ist anfällig für Angriffe und keine Best Practices.
Min
8
@Min, die akzeptierte Antwort ist nicht anfälliger für Angriffe als diese Antwort. Vielleicht haben Sie gedacht, es würde string.Format verwenden - das ist es nicht.
Simon MᶜKenzie
1
Dies sollte als Antwort markiert werden! Dies ist die einzig richtige Antwort, da sie mit DateTime funktioniert.
Sven
219

Es stellt sich heraus, dass dies funktioniert.

var firstName = "John";
var id = 12;
var sql = "Update [User] SET FirstName = {0} WHERE Id = {1}";
ctx.Database.ExecuteSqlCommand(sql, firstName, id);
Jessegavin
quelle
12
Dies funktioniert, aber dieser Mechanismus ermöglicht die SQL-Injection und verhindert auch, dass die Datenbank einen Ausführungsplan wiederverwendet, wenn die Anweisung erneut angezeigt wird, jedoch mit unterschiedlichen Werten.
Greg Biles
95
@ GregB Ich glaube nicht, dass Sie hier richtig sind. Ich habe getestet, dass es mir beispielsweise nicht erlaubt, ein String-Literal vorzeitig zu beenden. Außerdem habe ich mir den Quellcode angesehen und festgestellt, dass er DbCommand.CreateParameter verwendet, um alle Rohwerte in Parameter zu packen. Also keine SQL-Injection und ein schöner prägnanter Methodenaufruf.
Josh Gallagher
7
@ JoshGallagher Ja, du hast recht. Ich dachte an eine Zeichenfolge. Format-Szenario, das dies zusammensetzt.
Greg Biles
6
Es funktioniert NICHT ab SQL Server 2008 R2. Sie haben @ p0, @ p2, ..., @pN anstelle der von Ihnen übergebenen Parameter. Verwenden Sie SqlParameter("@paramName", value)stattdessen.
Arnthor
Ich kann nicht glauben, dass niemand die Auswirkungen dieser Abfrage auf die Leistung erwähnt hat, wenn die Datenbanktabellenspalten als ANSI-Varchar angegeben sind. .Net-Zeichenfolgen sind Unicode-Zeichen. Dies bedeutet, dass Parameter als nvarchar an den Server übergeben werden. Dies würde zu erheblichen Leistungsproblemen führen, da die Datenschicht eine Datenübersetzung durchführen muss. Sie sollten sich an den Ansatz von SqlParameter halten und die Datentypen angeben.
Akd
68

Du kannst entweder:

1) Übergeben Sie Rohargumente und verwenden Sie die {0} -Syntax. Z.B:

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

2) Übergeben Sie die Argumente der DbParameter-Unterklasse und verwenden Sie die @ ParamName-Syntax.

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

Wenn Sie die erste Syntax verwenden, wird EF Ihre Argumente tatsächlich mit DbParamater-Klassen umschließen, ihnen Namen zuweisen und {0} durch den generierten Parameternamen ersetzen.

Die erste Syntax wird bevorzugt, da Sie keine Factory verwenden müssen oder nicht wissen, welche Art von DbParamaters erstellt werden soll (SqlParameter, OracleParamter usw.).

Will Brown
quelle
6
Upvoted für die Erwähnung, dass die {0} -Syntax datenbankunabhängig ist. "... Sie müssen keine Fabrik benutzen oder wissen, welche Art von DbParamaters zu erstellen ist ..."
Makotosan
Szenario 1 ist zugunsten einer interpolierten Version veraltet. Äquivalent ist jetzt: DbContext.Database.ExecuteSqlInterpolated ($ "StoredProcedureName {paramName}");
ScottB
20

Die anderen Antworten funktionieren bei Verwendung von Oracle nicht. Sie müssen :anstelle von verwenden @.

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

context.Database.ExecuteSqlCommand(
   sql,
   new OracleParameter(":FirstName", firstName), 
   new OracleParameter(":Id", id));
Ryan M.
quelle
Gott sei Dank benutzt niemand Oracle. Na nicht freiwillig! EDIT: Entschuldigung für den späten Witz! EDIT: Entschuldigung für den schlechten Witz!
Chris Bordeman
18

Versuchen Sie dies (bearbeitet):

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

Die vorherige Idee war falsch.

Ladislav Mrnka
quelle
Wenn ich das mache, erhalte ich die folgende Fehlermeldung: "Vom Objekttyp System.Data.Objects.ObjectParameter zu einem bekannten nativen Typ eines verwalteten Anbieters ist keine Zuordnung vorhanden."
Jessegavin
Entschuldigung, mein Fehler. Verwenden Sie DbParameter.
Ladislav Mrnka
7
DbParameter ist abstrakt. Sie müssen SqlParameter oder eine DbFactory verwenden, um einen DbParameter zu erstellen.
Jrummell
12

Für Entity Framework Core 2.0 oder höher ist der richtige Weg, dies zu tun:

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

Beachten Sie, dass Entity Framework die beiden Parameter für Sie erstellt, sodass Sie vor SQL Injection geschützt sind.

Beachten Sie auch, dass es NICHT ist:

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

weil dies Sie NICHT vor SQL Injection schützt und keine Parameter erzeugt werden.

Sehen Sie dies für mehr.

Greg Gum
quelle
3
Ich liebe .NET Core 2.0 so sehr, dass ich Freudentränen habe: ')
Joshua Kemmerer
Ich kann die Begeisterung einiger Leute sehen, da .NET Core 2.0 für mich bisher eine reibungslosere Fahrt war.
Paul Carlton
Wie ist der erste nicht anfällig für SQL-Injection? Beide verwenden die C # -Stringinterpolation. Der erste wäre meines Wissens nicht in der Lage, die String-Erweiterung zu verhindern. Ich vermute, Sie meinten das soctx.Database.ExecuteSqlCommand("Update [User] SET FirstName = {firstName} WHERE Id = {id}", firstName, id);
CodeNaked
@CodeNaked, Nein, das habe ich nicht gemeint. EF ist sich des Problems bewusst und erstellt zwei tatsächliche Parameter, um Sie zu schützen. Es wird also nicht nur eine Zeichenfolge durchlaufen. Siehe Link oben für Details. Wenn Sie es in VS versuchen, gibt meine Version keine Warnung zu SQL Injection aus, die andere jedoch.
Greg Gum
1
@ GregGum - Bis ungefähr FormattableString. Du hast recht und das ist ziemlich cool!
CodeNaked
4

Vereinfachte Version für Oracle. Wenn Sie OracleParameter nicht erstellen möchten

var sql = "Update [User] SET FirstName = :p0 WHERE Id = :p1";
context.Database.ExecuteSqlCommand(sql, firstName, id);
Andreas
quelle
2
In SQL Server verwende ich @ p0 anstelle von: 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 });

Das ist so einfach !!!

Bild zur Kenntnis der Parameterreferenz

Geben Sie hier die Bildbeschreibung ein

zaw
quelle
2

Für die asynchrone Methode ("ExecuteSqlCommandAsync") können Sie sie folgendermaßen verwenden:

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
quelle
Dies ist nicht db-agnostisch, sondern funktioniert nur für MS-SQL Server. Es wird für Oracle oder PG fehlschlagen.
ANeves
1

Wenn Ihre zugrunde liegenden Datenbankdatentypen varchar sind, sollten Sie sich an den folgenden Ansatz halten. Andernfalls hätte die Abfrage einen enormen Einfluss auf die Leistung.

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

Sie können den SQL-Profiler überprüfen, um den Unterschied festzustellen.

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

Verwendung:

var names = db.Database.SqlQueryPrm<string>("select name from position_category where id_key=@id_key", new { id_key = "mgr" }).ToList();
Neco
quelle
7
Gibt es eine Möglichkeit, diesen Code zu erklären und warum er eine Antwort auf die gestellte Frage ist, damit diejenigen, die ihn später finden, ihn verstehen können?
Andrew Barber
1
Problem mit der {0} -Syntax, dass Sie die Lesbarkeit verlieren - ich persönlich mag es nicht wirklich. Problem beim Übergeben von SqlParameters, für das Sie eine konkrete Implementierung von DbParameter angeben müssen (SqlParameter, OracleParameter usw.). Mit dem bereitgestellten Beispiel können Sie diese Probleme vermeiden.
Neco
3
Ich denke, das ist eine gültige Meinung, aber Sie haben die hier tatsächlich gestellte Frage nicht beantwortet . Übergeben von Parametern an ExecuteSqlCommand()Sie sollten sicher sein, die spezifische Frage zu beantworten, die beim Posten von Antworten gestellt wird.
Andrew Barber
3
Downvoted, weil es unnötig kompliziert ist (z. B. Reflexion verwendet, das Rad neu erfindet, verschiedene Datenbankanbieter nicht berücksichtigt)
ein Phu
Up-Voted, weil es eine der möglichen Lösungen zeigt. Ich bin sicher, dass ExecuteSqlCommand Parameter genauso akzeptiert wie SqlQuery. Ich mag auch den Dapper.net-Stil, bei dem die Parameter übergeben werden.
Zar Shardan
0

Mehrere Parameter in einer gespeicherten Prozedur mit mehreren Parametern in vb:

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

Gespeicherte Prozeduren können wie folgt ausgeführt werden

 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
quelle
Vergessen Sie nicht, System.Data.SqlClient
FlyingV
0

Für .NET Core 2.2 können Sie FormattableStringfür dynamisches SQL verwenden.

//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);
Leistungsschalter
quelle
Vergessen Sie nicht, System.Data.SqlClient
FlyingV