Как передать параметры в метод 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, а документация для этого метода не дает примеров того, как выполнить параметризованный запрос.

Как правильно указать параметры?

jessegavin
источник

Ответы:

295

Попробуй это:

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

ctx.Database.ExecuteSqlCommand(
    sql,
    new SqlParameter("@FirstName", firstname),
    new SqlParameter("@Id", id));
Роберт Те Каат
источник
2
Это действительно должен быть ответ, помеченный как правильный, приведенный выше подвержен атакам и не является наилучшей практикой.
Мин
8
@ Минь, принятый ответ не более подвержен атакам, чем этот ответ. Может быть, вы думали, что он использует строку. Формат - это не так.
Саймон 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);
jessegavin
источник
13
Это будет работать, но этот механизм позволяет вводить SQL, а также не позволяет базе данных повторно использовать план выполнения, когда оператор снова появляется, но с другими значениями.
Грег Байлс
95
@GregB Я не думаю, что вы правы здесь. Я проверял, что это не позволит мне, например, досрочно завершить строковый литерал. Кроме того, я посмотрел на исходный код и обнаружил, что он использует DbCommand.CreateParameter, чтобы обернуть любые необработанные значения в параметры. Так что никаких SQL-инъекций и приятного лаконичного вызова методов.
Джош Галлахер
7
@JoshGallagher Да, ты прав. Я думал о последовательности. Сценарий формата, соединяющий это.
Грег Байлс
6
Это не работает с SQL Server 2008 R2. У вас будут @ p0, @ p2, ..., @pN вместо параметров, которые вы передали. Используйте SqlParameter("@paramName", value)вместо этого.
Арнтор
Не могу поверить, что никто не упомянул влияние этого запроса на производительность, если столбцы таблицы базы данных указаны как ANSI varchar. Строки .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] создавать ...»
Makotosan
Сценарий 1 устарел в пользу интерполированной версии. Эквивалент теперь: DbContext.Database.ExecuteSqlInterpolated ($ "StoredProcedureName {paramName}");
ScottB
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. Ну не добровольно! РЕДАКТИРОВАТЬ: Извинения за позднюю шутку! РЕДАКТИРОВАТЬ: Извинения за плохую шутку!
Крис Бордеман
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

Для Entity Framework Core 2.0 или выше, правильный способ сделать это:

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

Обратите внимание, что Entity Framework предоставит вам два параметра, поэтому вы защищены от Sql Injection.

Также обратите внимание, что это НЕ:

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);
Andreas
источник
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)
    });
Маг
источник
Это не зависит от базы данных, оно будет работать только для MS-SQL Server. Это не удастся для Oracle или PG.
Ноябрь
1

Если ваши базовые типы данных базы данных varchar, вам следует придерживаться подхода, приведенного ниже. В противном случае запрос будет иметь огромное влияние на производительность.

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 Profiler, чтобы увидеть разницу.

AKD
источник
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();
Neco
источник
7
Есть ли шанс, что вы сможете объяснить этот код и почему он является ответом на поставленный вопрос, чтобы те, кто придет и обнаружит его позже, могли его понять?
Эндрю Барбер
1
Проблема с синтаксисом {0}, из-за которой вы теряете читабельность - лично мне это не очень нравится. Проблема с передачей SqlParameters, что вам нужно указать конкретную реализацию DbParameter (SqlParameter, OracleParameter и т. Д.). Приведенный пример позволяет избежать этих проблем.
Neco
3
Я думаю, это правильное мнение, но вы еще не ответили на вопрос, который на самом деле задают здесь; Как передать параметры ExecuteSqlCommand()Вам следует обязательно ответить на конкретный вопрос, задаваемый при публикации ответов.
Эндрю Барбер
3
Понижено, потому что это неоправданно сложно (например, использует отражение, заново изобретает колесо, не учитывает разных поставщиков баз данных)
phu
Проголосовал, потому что это показывает одно из возможных решений. Я уверен, что 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