Як передати параметри методу DbContext.Database.ExecuteSqlCommand?


222

Припустимо, у мене є обґрунтована потреба безпосередньо виконувати команду sql в Entity Framework. У мене виникають проблеми з з'ясуванням способів використання параметрів у моєму операторі sql. Наступний приклад (не мій реальний приклад) не працює.

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

Метод ExecuteSqlCommand не дозволяє передавати такі параметри, як в ADO.Net, і документація цього методу не дає прикладів виконання параметризованого запиту.

Як правильно вказати параметри?

Відповіді:


294

Спробуйте це:

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

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

2
Це дійсно має бути відповідь, позначений правильним, той, хто вище, схильний до атак і не є найкращою практикою.
Мін.

8
@Min, прийнята відповідь не більш схильна до атак, ніж ця відповідь. Можливо, ви думали, що він використовує string.Format - це не так.
Simon MᶜKenzie

1
Це слід позначити як відповідь! Це одна і єдина правильна відповідь, оскільки вона працює з DateTime.
Свен

219

Виявляється, це працює.

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

12
Це буде працювати, але цей механізм дозволяє вводити SQL, а також заважає базі даних повторно використовувати план виконання, коли оператор з'явиться знову, але з різними значеннями.
Грег Білес

95
@GregB Я не думаю, що ти тут правильний. Я перевірив, що це не дозволить мені, наприклад, достроково закінчити буквений рядок. Крім того, я переглянув вихідний код і виявив, що він використовує DbCommand.CreateParameter, щоб обернути будь-які вихідні значення в параметри. Тож ніякої ін'єкції SQL та приємного виклику методу.
Джош Галлахер

7
@JoshGallagher Так, ти маєш рацію. Я думав про string.Format сценарій поєднання цього.
Грег Білес

6
Він НЕ працює, як для SQL Server 2008 R2. Замість параметрів, які ви передали, у вас буде @ p0, @ p2, ..., @pN. Використовуйте SqlParameter("@paramName", value)замість цього.
Арнтор

Не можу повірити, що ніхто не згадував про ефективність цього запиту на продуктивність, якщо стовпці таблиці бази даних вказані як варшар ANSI. .Net рядки є unicode означає, що параметри будуть передані серверу як nvarchar. Це призведе до значної проблеми з ефективністю, оскільки рівень даних повинен здійснювати переклад даних. Вам слід дотримуватися підходу SqlParameter та вказувати типи даних.
акд

68

Ви можете:

1) Передайте необроблені аргументи і використовуйте синтаксис {0}. Наприклад:

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

2) Передайте аргументи підкласу DbParameter і використовуйте синтаксис @ParamName.

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

Якщо ви використовуєте перший синтаксис, EF фактично оберне ваші аргументи класами DbParamater, призначить їм імена та замінить {0} на створене ім'я параметра.

Перший синтаксис, якщо бажано, тому що вам не потрібно використовувати фабрику або знати, який тип DbParamaters створювати (SqlParameter, OracleParamter тощо).


6
Запропоновано згадати, що синтаксис {0} є агностиком бази даних. "... вам не потрібно [sic] використовувати фабрику або знати, який тип DbParamaters [sic] створити ..."
Макотосан

Сценарій 1 застарілий на користь інтерпольованої версії. Зараз еквівалент: DbContext.Database.ExecuteSqlInterpolated ($ "StoredProcedureName {paramName}");
СкотБ

20

Інші відповіді не працюють під час використання Oracle. Ви повинні використовувати :замість @.

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

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

Слава Богу, ніхто не використовує Oracle. Ну не добровільно! EDIT: Вибачення за пізній жарт! EDIT: Вибачення за поганий жарт!
Кріс Бордеман

18

Спробуйте це (відредаговано):

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

Попередня думка була помилковою.


Коли я це роблю, я отримую таку помилку: "Немає відображення від типу об'єкта System.Data.Objects.ObjectParameter до відомого власного типу керованого постачальника."
jessegavin

Вибачте за мою помилку. Використовуйте DbParameter.
Ладислав Мрнка

7
DbParameter є абстрактним. Вам доведеться використовувати SqlParameter або використовувати DbFactory для створення DbParameter.
jrummell

12

Для сутності Framework Core 2.0 або вище, правильний спосіб це зробити:

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

Зауважте, що Entity Framework буде виробляти два параметри для вас, тому ви захищені від ін'єкції Sql.

Також зауважте, що НЕ:

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

тому що це НЕ захищає вас від ін'єкції Sql, і параметри не виробляються.

Дивіться це докладніше.


3
Я так люблю .NET Core 2.0, що дає мені сльози радості: ')
Джошуа Кеммерер

Я можу побачити ентузіазм людей, оскільки .NET Core 2.0 до цього часу був більш плавним для мене.
Пол Карлтон

Як перший не вразливий до ін'єкції SQL? Обидва використовують інтерполяцію рядків C #. Перший, наскільки я не знаю, не зможе запобігти розширенню рядків. Я підозрюю, що ви це мали на метіctx.Database.ExecuteSqlCommand("Update [User] SET FirstName = {firstName} WHERE Id = {id}", firstName, id);
CodeNaked

@CodeNaked, Ні, я цього не мав на увазі. EF усвідомлює проблему та створює два фактичні параметри для захисту вас. Отже, це не просто рядок, який проходить через. Докладніше див. За посиланням вище. Якщо ви спробуєте це у VS, моя версія не видасть попередження про Sql Injection, а інша буде.
Грег Гум

1
@GregGum - TIL о FormattableString. Ви маєте рацію, і це дуже круто!
CodeNaked

4

Спрощена версія для Oracle. Якщо ви не хочете створити OracleParameter

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

2
У SQL Server я використовую @ p0 замість: p0.
Марек Мальчевський

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

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

Це так просто !!!

Зображення для отримання посилання на параметр

введіть тут опис зображення


2

Для методу асинхронізації ("ExecuteSqlCommandAsync") ви можете використовувати його так:

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

Це не db-агностик, він працюватиме лише для MS-SQL Server. Це не вдасться для Oracle або PG.
ANeves

1

Якщо основні типи даних бази даних є варшарними, тоді слід дотримуватися підходу нижче. Інакше запит матиме величезний вплив на продуктивність.

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

Ви можете перевірити Sql-профілер, щоб побачити різницю.


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

Використання:

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

7
Будь-який шанс ви могли б пояснити цим кодом і чому це відповідь на поставлене питання, щоб ті, хто приходить і знайде це пізніше, зможуть це зрозуміти?
Ендрю Барбер

1
Проблема з {0} синтаксисом, що ви втратили читабельність - мені особисто це не дуже подобається. Проблема з проходженням SqlParameters, що вам потрібно вказати конкретну реалізацію DbParameter (SqlParameter, OracleParameter тощо). Наведений приклад дозволяє уникнути цих проблем.
Neco

3
Я думаю, що це правдива думка, але ви не відповіли на запитання, яке насправді тут задають ; Як передавати параметри. ExecuteSqlCommand()Ви повинні обов'язково відповісти на певне запитання, коли ви публікуєте відповіді.
Ендрю Барбер

3
Захищений тому, що це надмірно складне (наприклад, використовує роздуми, переосмислить колесо, не вдається врахувати різних постачальників баз даних)
пн

Оголошено, оскільки воно відображає одне з можливих рішень. Я впевнений, що ExecuteSqlCommand приймає параметри так само, як це робить SqlQuery. Мені також подобається стиль передачі параметрів Dapper.net.
Зар Шардан

0

Кілька парам у збереженій процедурі, що має кілька парам в vb:

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

0

Збережені процедури можна виконати як нижче

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

Не забудьте скористатися System.Data.SqlClient
FlyingV

0

Для .NET Core 2.2 ви можете використовувати FormattableStringдля динамічного 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);

Не забудьте скористатися System.Data.SqlClient
FlyingV
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.