Como passar parâmetros para o método DbContext.Database.ExecuteSqlCommand?

222

Vamos supor que eu tenha uma necessidade válida de executar diretamente um comando sql no Entity Framework. Estou tendo problemas para descobrir como usar parâmetros na minha instrução sql. O exemplo a seguir (não o meu exemplo real) não funciona.

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

O método ExecuteSqlCommand não permite que você transmita parâmetros nomeados como no ADO.Net e a documentação desse método não fornece exemplos de como executar uma consulta parametrizada.

Como especifico os parâmetros corretamente?

jessegavin
fonte

Respostas:

294

Tente o seguinte:

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
fonte
2
Essa deve ser a resposta correta, a que está acima é propensa a ataques e não é uma prática recomendada.
Min
8
@Min, a resposta aceita não é mais propensa a ataques do que esta resposta. Talvez você tenha pensado que estava usando string.Format - não é.
Simon MᶜKenzie
1
Isso deve ser marcado como resposta! Essa é a única e correta resposta, pois funciona com o DateTime.
Sven
219

Acontece que isso funciona.

var firstName = "John";
var id = 12;
var sql = "Update [User] SET FirstName = {0} WHERE Id = {1}";
ctx.Database.ExecuteSqlCommand(sql, firstName, id);
jessegavin
fonte
12
Isso funcionará, mas esse mecanismo permite a injeção de SQL e também impede que o banco de dados reutilize um plano de execução quando a instrução aparecer novamente, mas com valores diferentes.
precisa
95
@ GregB Eu não acho que você está correto aqui. Eu testei que isso não me permitirá, por exemplo, encerrar uma string literal cedo. Além disso, observei o código-fonte e descobri que ele está usando DbCommand.CreateParameter para agrupar quaisquer valores brutos em parâmetros. Portanto, nenhuma injeção de SQL e uma boa invocação sucinta de método.
Josh Gallagher
7
@JoshGallagher Sim, você está certo. Eu estava pensando em uma string.Format cenário colocando isso juntos.
precisa
6
NÃO funciona a partir do SQL Server 2008 R2. Você terá @ p0, @ p2, ..., @pN em vez dos parâmetros que você passou. Use em SqlParameter("@paramName", value)vez disso.
precisa
Não posso acreditar que ninguém tenha mencionado o impacto no desempenho dessa consulta se as colunas da tabela do banco de dados forem especificadas como varchar ANSI. As cadeias .Net são unicode significa que os paramaters serão passados ​​para o servidor como nvarchar. Isso causaria um problema de desempenho significativo, pois a camada de dados deve executar a conversão de dados. Você deve seguir a abordagem do SqlParameter e especificar os tipos de dados.
precisa
68

Você também pode:

1) Passe argumentos brutos e use a sintaxe {0}. Por exemplo:

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

2) Passe argumentos da subclasse DbParameter e use a sintaxe @ParamName.

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

Se você usar a primeira sintaxe, o EF encerrará seus argumentos com as classes DbParamater, atribuirá nomes a eles e substituirá {0} pelo nome do parâmetro gerado.

A primeira sintaxe, se preferir, porque você não precisa usar uma fábrica ou saber que tipo de DbParamaters criar (SqlParameter, OracleParamter, etc.).

Will Brown
fonte
6
Promovido o voto por mencionar que a sintaxe {0} é independente de banco de dados. "... você não [sic] necessidade de usar uma fábrica ou saber que tipo de DbParamaters [sic] para criar ..."
Makotosan
O cenário 1 foi descontinuado em favor de uma versão interpolada. Agora o equivalente é: DbContext.Database.ExecuteSqlInterpolated ($ "StoredProcedureName {paramName}");
ScottB 26/01
20

As outras respostas não funcionam ao usar o Oracle. Você precisa usar em :vez de @.

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

context.Database.ExecuteSqlCommand(
   sql,
   new OracleParameter(":FirstName", firstName), 
   new OracleParameter(":Id", id));
Ryan M
fonte
Graças a Deus ninguém usa Oracle. Bem, não voluntariamente! EDIT: Desculpas pela piada tardia! EDIT: Desculpas pela piada de mau gosto!
Chris Bordeman
18

Tente isto (editado):

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

A ideia anterior estava errada.

Ladislav Mrnka
fonte
Quando faço isso, recebo o seguinte erro: "Não existe mapeamento do tipo de objeto System.Data.Objects.ObjectParameter para um tipo nativo de provedor gerenciado conhecido".
Jessegavin
Desculpe meu erro. Use DbParameter.
Ladislav Mrnka 29/03
7
DbParameter é abstrato. Você precisará usar SqlParameter ou usar um DbFactory para criar um DbParameter.
jrummell
12

Para a entidade Framework Core 2.0 ou superior, a maneira correta de fazer isso é:

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

Observe que o Entity Framework produzirá os dois parâmetros para você, para que você esteja protegido contra a injeção de SQL.

Observe também que NÃO é:

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

porque isso NÃO o protege da injeção SQL e nenhum parâmetro é produzido.

Veja isso para mais.

Greg Gum
fonte
3
Eu amo .NET Núcleo 2.0 tanto que me dá lágrimas de alegria: ')
Joshua Kemmerer
Percebo o entusiasmo de algumas pessoas, pois o .NET Core 2.0 tem sido um caminho mais suave para mim até agora.
Paul Carlton
Como o primeiro não é vulnerável à injeção de SQL? Ambos usam interpolação de string C #. O primeiro não seria capaz de antecipar a expansão da string, tanto quanto eu sei. Eu suspeito que você queria que fosse isso.ctx.Database.ExecuteSqlCommand("Update [User] SET FirstName = {firstName} WHERE Id = {id}", firstName, id);
CodeNaked
@ CodeNaked, não, eu não quis dizer isso. A EF está ciente do problema e cria dois parâmetros reais para protegê-lo. Portanto, não é apenas uma string que é passada. Veja o link acima para detalhes. Se você tentar no VS, minha versão não emitirá um aviso sobre o Sql Injection e a outra.
Greg Gum
1
@GregGum - TIL sobre FormattableString. Você está certo e isso é muito legal!
CodeNaked
4

Versão simplificada para Oracle. Se você não deseja criar OracleParameter

var sql = "Update [User] SET FirstName = :p0 WHERE Id = :p1";
context.Database.ExecuteSqlCommand(sql, firstName, id);
Andreas
fonte
2
No SQL Server, uso @ p0 em vez de: 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 });

Isso é tão simples !!!

Imagem para conhecer a referência de parâmetro

insira a descrição da imagem aqui

zaw
fonte
2

Para o método async ("ExecuteSqlCommandAsync"), você pode usá-lo assim:

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
fonte
Isso não é independente do db, ele funcionará apenas para o MS-SQL Server. Falhará para Oracle ou PG.
#
1

Se seus tipos de dados de banco de dados subjacentes são varchar, você deve seguir a abordagem abaixo. Caso contrário, a consulta teria um enorme impacto no desempenho.

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

Você pode verificar o Sql Profiler para ver a diferença.

akd
fonte
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();
Neco
fonte
7
Alguma chance de você poder explicar esse código e por que ele é uma resposta para a pergunta feita, para que aqueles que o encontrarem mais tarde possam entendê-lo?
Andrew Barber
1
Problema com a sintaxe {0} de que você perde a legibilidade - eu pessoalmente não gosto disso. Problema ao passar SqlParameters que você precisa especificar a implementação concreta de DbParameter (SqlParameter, OracleParameter etc.). O exemplo fornecido permite evitar esses problemas.
Neco
3
Acho que é uma opinião válida, mas você não respondeu à pergunta que está sendo feita aqui; Como passar parâmetros para ExecuteSqlCommand()Você não se esqueça de responder à pergunta específica que está sendo feita quando publicar as respostas.
Andrew Barber
3
Downvoted porque é desnecessariamente complicado (por exemplo, utiliza reflexão, re-inventar a roda, não conta para os prestadores de diferentes bancos de dados)
um phu
Up-vote porque mostra uma das soluções possíveis. Tenho certeza que ExecuteSqlCommand aceita parâmetros da mesma maneira que SqlQuery. Também gosto do estilo Dapper.net de passar os parâmetros.
Zar Shardan
0

Vários parâmetros em um procedimento armazenado que possui vários parâmetros na vb:

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

Procedimentos armazenados podem ser executados como abaixo

 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
fonte
Não se esqueça de usar o System.Data.SqlClient
FlyingV
0

Para o .NET Core 2.2, você pode usar FormattableStringo SQL dinâmico.

//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);
Disjuntor
fonte
Não se esqueça de usar o System.Data.SqlClient
FlyingV