Jak przekazać parametry do metody DbContext.Database.ExecuteSqlCommand?


222

Załóżmy, że mam uzasadnioną potrzebę bezpośredniego wykonania komendy sql w Entity Framework. Mam problem z ustaleniem, jak używać parametrów w mojej instrukcji SQL. Poniższy przykład (nie mój prawdziwy przykład) nie działa.

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

Metoda ExecuteSqlCommand nie pozwala na przekazanie nazwanych parametrów, jak w ADO.Net, a dokumentacja tej metody nie zawiera żadnych przykładów wykonywania sparametryzowanego zapytania.

Jak poprawnie określić parametry?

Odpowiedzi:


294

Spróbuj tego:

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

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

2
To naprawdę powinna być odpowiedź oznaczona jako poprawna, powyższa jest podatna na ataki i nie jest najlepszą praktyką.
Min.

8
@Min, zaakceptowana odpowiedź nie jest bardziej podatna na ataki niż ta odpowiedź. Może myślałeś, że używa sznurka. Format - nie jest.
Simon MᶜKenzie

1
To powinno być oznaczone jako odpowiedź! To jedyna poprawna odpowiedź, ponieważ działa z DateTime.
Sven

219

Okazuje się, że to działa.

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

12
To zadziała, ale ten mechanizm pozwala na wstrzyknięcie SQL, a także zapobiega ponownemu użyciu przez bazę danych planu wykonania, gdy instrukcja pojawi się ponownie, ale z różnymi wartościami.
Greg Biles,

95
@GregB Nie sądzę, że masz rację tutaj. Testowałem, że nie pozwoli mi to na przykład wcześniej zakończyć dosłownego ciągu znaków. Ponadto spojrzałem na kod źródłowy i stwierdziłem, że używa on DbCommand.CreateParameter do zawijania dowolnych surowych wartości do parametrów. Więc bez iniekcji SQL i ładne zwięzłe wywołanie metody.
Josh Gallagher

7
@JoshGallagher Tak, masz rację. Myślałem o łańcuchu. Sformatowany scenariusz składa się w całość.
Greg Biles,

6
NIE działa od wersji SQL Server 2008 R2. Będziesz mieć @ p0, @ p2, ..., @pN zamiast parametrów, które podałeś. Użyj SqlParameter("@paramName", value)zamiast tego.
Arnthor,

Nie mogę uwierzyć, że nikt nie wspomniał o wpływie tego zapytania na wydajność, jeśli kolumny tabeli bazy danych są określone jako ANSI varchar. .Net ciągi są Unicode, oznacza, że ​​parametry będą przekazywane do serwera jako nvarchar. Spowodowałoby to znaczny problem z wydajnością, ponieważ warstwa danych musi wykonywać translację danych. Powinieneś trzymać się SqlParameter i określić typy danych.
akd

68

Możesz albo:

1) Przekaż nieprzetworzone argumenty i użyj składni {0}. Na przykład:

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

2) Przekaż argumenty podklasy DbParameter i użyj składni @ParamName.

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

Jeśli użyjesz pierwszej składni, EF faktycznie otoczy twoje argumenty klasami DbParamater, przypisze im nazwy i zastąpi {0} wygenerowaną nazwą parametru.

Pierwsza składnia, jeśli preferowana, ponieważ nie trzeba używać fabryki ani wiedzieć, jaki typ DbParamaters należy utworzyć (SqlParameter, OracleParamter itp.).


6
Za wzmiankę o wspominaniu, że składnia {0} jest agnostyczna dla bazy danych. „... nie musisz [sic] korzystać z fabryki ani wiedzieć, jakiego typu DbParamaters [sic] stworzyć…”
Makotosan,

Scenariusz 1 jest przestarzały na korzyść wersji interpolowanej. Odpowiednikiem jest teraz: DbContext.Database.ExecuteSqlInterpolated ($ „StoredProcedureName {paramName}”);
ScottB

20

Inne odpowiedzi nie działają podczas korzystania z Oracle. Musisz użyć :zamiast @.

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

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

Dzięki Bogu nikt nie używa Oracle. Cóż, nie dobrowolnie! EDYCJA: Przepraszam za późny żart! EDYCJA: Przepraszam za zły żart!
Chris Bordeman,

18

Spróbuj tego (edytowane):

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

Poprzedni pomysł był zły.


Gdy to robię, pojawia się następujący błąd: „Nie istnieje mapowanie z typu obiektu System.Data.Objects.ObjectParameter na typ rodzimy znanego zarządzanego dostawcy”.
jessegavin

Przepraszam, mój błąd. Użyj DbParameter.
Ladislav Mrnka

7
DbParameter jest abstrakcyjny. Będziesz musiał użyć SqlParameter lub DbFactory, aby utworzyć DbParameter.
jrummell

12

W przypadku jednostki Framework Core 2.0 lub nowszej poprawny sposób to:

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

Zauważ, że Entity Framework wygeneruje dla ciebie dwa parametry, więc jesteś chroniony przed wstrzykiwaniem SQL.

Pamiętaj również, że NIE:

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

ponieważ to NIE chroni cię przed wstrzykiwaniem SQL i nie są generowane żadne parametry.

Zobacz to po więcej.


3
Uwielbiam .NET Core 2.0 tak bardzo, że daje mi łzy radości: ')
Joshua Kemmerer

Widzę entuzjazm niektórych ludzi, ponieważ .NET Core 2.0 był dla mnie bardziej płynną jazdą.
Paul Carlton,

W jaki sposób pierwszy nie jest podatny na wstrzyknięcie SQL? Oba używają interpolacji ciągów C #. O ile mi wiadomo, pierwszy nie byłby w stanie zapobiec rozszerzeniu łańcucha. Podejrzewam, że miałeś to na myślictx.Database.ExecuteSqlCommand("Update [User] SET FirstName = {firstName} WHERE Id = {id}", firstName, id);
CodeNaked

@CodeNaked, Nie, nie miałem tego na myśli. EF jest świadomy problemu i tworzy dwa rzeczywiste parametry w celu ochrony. Więc nie jest to tylko ciąg, który jest przekazywany. Zobacz link powyżej, aby uzyskać szczegółowe informacje. Jeśli spróbujesz w VS, moja wersja nie wyświetli ostrzeżenia o wstrzyknięciu SQL, a druga zrobi to.
Greg Gum

1
@GregGum - TIL about FormattableString. Masz rację i to całkiem fajne!
CodeNaked

4

Uproszczona wersja dla Oracle. Jeśli nie chcesz tworzyć OracleParameter

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

2
W SQL Server używam @ p0 zamiast: 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 });

To takie proste !!!

Obraz do poznania odniesienia do parametru

wprowadź opis zdjęcia tutaj


2

W przypadku metody asynchronicznej („ExecuteSqlCommandAsync”) możesz użyć jej w następujący sposób:

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

To nie jest db-agnostic, będzie działać tylko dla MS-SQL Server. Nie powiedzie się w przypadku Oracle lub PG.
ANeves

1

Jeśli bazowymi typami danych są varchar, powinieneś trzymać się poniższego podejścia. W przeciwnym razie zapytanie miałoby ogromny wpływ na wydajność.

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

Możesz sprawdzić profil Sql, aby zobaczyć różnicę.


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

Stosowanie:

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

7
Czy masz szansę wyjaśnić ten kod i dlaczego jest on odpowiedzią na postawione pytanie, aby ci, którzy przyjdą i znajdą go później, mogli go zrozumieć?
Andrew Barber,

1
Problem ze składnią {0} polegającą na tym, że tracisz czytelność - osobiście nie lubię tego. Problem z przekazywaniem SqlParameters, które musisz określić konkretną implementację DbParameter (SqlParameter, OracleParameter itp.). Podany przykład pozwala uniknąć tych problemów.
Neco

3
Wydaje mi się, że to słuszna opinia, ale nie odpowiedzieliście na pytanie, które tu jest zadawane ; Jak przekazać parametry ExecuteSqlCommand()Powinieneś być pewien, że odpowiesz na konkretne pytanie zadawane podczas publikowania odpowiedzi.
Andrew Barber

3
Oddany do głosowania, ponieważ jest niepotrzebnie skomplikowany (np. Używa refleksji, ponownego wynalezienia koła, nie uwzględnia różnych dostawców baz danych)
phu

Podniosło głos, ponieważ pokazuje jedno z możliwych rozwiązań. Jestem pewien, że ExecuteSqlCommand akceptuje parametry w taki sam sposób, jak SqlQuery. Podoba mi się również styl przekazywania parametrów przez Dapper.net.
Zar Shardan

0

Wiele parametrów w procedurze przechowywanej, która ma wiele parametrów w vb:

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

0

Procedury przechowywane można wykonać jak poniżej

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

Nie zapomnij skorzystać z System.Data.SqlClient
FlyingV

0

W przypadku .NET Core 2.2 można używać FormattableStringdo dynamicznego SQL.

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

Nie zapomnij skorzystać z System.Data.SqlClient
FlyingV
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.