¿Cómo pasar parámetros al método DbContext.Database.ExecuteSqlCommand?

222

Supongamos que tengo una necesidad válida de ejecutar directamente un comando sql en Entity Framework. Tengo problemas para descubrir cómo usar los parámetros en mi declaración sql. El siguiente ejemplo (no es mi ejemplo real) no funciona.

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

El método ExecuteSqlCommand no le permite pasar parámetros con nombre como en ADO.Net y la documentación de este método no da ningún ejemplo sobre cómo ejecutar una consulta parametrizada.

¿Cómo especifico los parámetros correctamente?

jessegavin
fuente

Respuestas:

294

Prueba esto:

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
fuente
2
Esta realmente debería ser la respuesta marcada como correcta, la anterior es propensa a los ataques y no son las mejores prácticas.
Min
8
@Min, la respuesta aceptada no es más propensa a los ataques que esta respuesta. Tal vez pensaste que estaba usando string.Format, no lo es.
Simon MᶜKenzie
1
Esto debe ser marcado como respuesta! Esta es la única respuesta correcta porque funciona con DateTime.
Sven
219

Resulta que esto funciona.

var firstName = "John";
var id = 12;
var sql = "Update [User] SET FirstName = {0} WHERE Id = {1}";
ctx.Database.ExecuteSqlCommand(sql, firstName, id);
jessegavin
fuente
12
Esto funcionará, pero este mecanismo permite la inyección de SQL y también evita que la base de datos reutilice un plan de ejecución cuando la instrucción vuelva a aparecer pero con valores diferentes.
Greg Biles el
95
@ GregB No creo que tengas razón aquí. He probado que no me permitirá, por ejemplo, terminar un string literal antes de tiempo. Además, miré el código fuente y descubrí que está usando DbCommand.CreateParameter para envolver cualquier valor en bruto en los parámetros. Entonces, no hay inyección de SQL, y una buena invocación de método sucinto.
Josh Gallagher
77
@JoshGallagher Sí, tienes razón. Estaba pensando en una secuencia. Escenario de formato que lo unía.
Greg Biles
66
NO funciona a partir de SQL Server 2008 R2. Tendrás @ p0, @ p2, ..., @pN en lugar de los parámetros que pasaste. Usar en su SqlParameter("@paramName", value)lugar.
Arnthor
No puedo creer que nadie haya mencionado el impacto en el rendimiento de esta consulta si las columnas de la tabla de la base de datos se especifican como ANSI varchar. Las cadenas .Net son unicode significa que los parámetros se pasarán al servidor como nvarchar. Esto causaría un problema de rendimiento significativo ya que la capa de datos debe realizar la traducción de datos. Debe seguir con el enfoque de SqlParameter y especificar los tipos de datos.
akd
68

Tu también puedes:

1) Pase argumentos en bruto y use la sintaxis {0}. P.ej:

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

2) Pase los argumentos de la subclase DbParameter y use la sintaxis @ParamName.

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

Si usa la primera sintaxis, EF en realidad envolverá sus argumentos con clases DbParamater, les asignará nombres y reemplazará {0} con el nombre del parámetro generado.

La primera sintaxis se prefiere porque no necesita usar una fábrica o saber qué tipo de DbParamaters crear (SqlParameter, OracleParamter, etc.).

Will Brown
fuente
66
Votado por mencionar que la sintaxis {0} es independiente de la base de datos. "... no necesitas usar una fábrica o saber qué tipo de DbParamaters [sic] crear ..."
Makotosan
El escenario 1 está en desuso en favor de una versión interpolada. El equivalente ahora es: DbContext.Database.ExecuteSqlInterpolated ($ "StoredProcedureName {paramName}");
ScottB
20

Las otras respuestas no funcionan cuando se usa Oracle. Necesitas usar en :lugar 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
fuente
Gracias a Dios nadie usa Oracle. ¡Pues no voluntariamente! EDITAR: ¡Disculpas por la broma tardía! EDITAR: ¡Disculpas por el mal chiste!
Chris Bordeman
18

Prueba esto (editado):

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

La idea anterior estaba mal.

Ladislav Mrnka
fuente
Cuando hago eso, aparece el siguiente error: "No existe una asignación del tipo de objeto System.Data.Objects.ObjectParameter a un tipo nativo de proveedor administrado conocido".
jessegavin
Perdón mi error. Use DbParameter.
Ladislav Mrnka
77
DbParameter es abstracto. Tendrá que usar SqlParameter o usar un DbFactory para crear un DbParameter.
jrummell
12

Para la entidad Framework Core 2.0 o superior, la forma correcta de hacer esto es:

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

Tenga en cuenta que Entity Framework producirá los dos parámetros para usted, por lo que está protegido de la inyección SQL.

También tenga en cuenta que NO es:

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

porque esto NO lo protege de la inyección SQL y no se producen parámetros.

Mira esto para más.

Greg Gum
fuente
3
Amo tanto .NET Core 2.0 que me da lágrimas de alegría: ')
Joshua Kemmerer
Puedo ver el entusiasmo de algunas personas ya que .NET Core 2.0 ha sido un viaje más suave para mí hasta ahora.
Paul Carlton
¿Cómo es que el primero no es vulnerable a la inyección SQL? Ambos usan la interpolación de cadenas C #. El primero no podría evitar la expansión de la cadena, que yo sepa. Sospecho que pretendías que fuera asíctx.Database.ExecuteSqlCommand("Update [User] SET FirstName = {firstName} WHERE Id = {id}", firstName, id);
CodeNaked
@CodeNaked, No, no quise decir eso. EF es consciente del problema y crea dos parámetros reales para protegerlo. Por lo tanto, no solo se pasa una cadena. Vea el enlace de arriba para más detalles. Si lo prueba en VS, mi versión no emitirá una advertencia sobre la inyección SQL, y la otra lo hará.
Greg Gum
1
@GregGum - TIL sobre FormattableString. Tienes razón y eso es genial!
CodeNaked el
4

Versión simplificada para Oracle. Si no quieres crear OracleParameter

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

¡Esto es muy simple!

Imagen para conocer la referencia del parámetro

ingrese la descripción de la imagen aquí

zaw
fuente
2

Para el Método asíncrono ("ExecuteSqlCommandAsync") puede usarlo así:

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
fuente
Esto no es db-agnostic, solo funcionará para MS-SQL Server. Fallará para Oracle o PG.
ANeves
1

Si los tipos de datos de la base de datos subyacentes son varchar, entonces debe seguir con el siguiente enfoque. De lo contrario, la consulta tendría un gran impacto en el rendimiento.

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

Puede consultar el perfilador SQL para ver la diferencia.

akd
fuente
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
fuente
77
¿Hay alguna posibilidad de que pueda explicar este código y por qué es una respuesta a la pregunta planteada, para que aquellos que vengan y encuentren esto más tarde puedan entenderlo?
Andrew Barber
1
Problema con la sintaxis {0} que pierde legibilidad. Personalmente no me gusta. Problema al pasar SqlParameters que necesita para especificar una implementación concreta de DbParameter (SqlParameter, OracleParameter, etc.). El ejemplo proporcionado le permite evitar estos problemas.
Neco
3
Supongo que es una opinión válida, pero no ha respondido la pregunta que realmente se hace aquí; Cómo pasar parámetros a ExecuteSqlCommand()Usted debe asegurarse de responder la pregunta específica que se le hace al publicar respuestas.
Andrew Barber
3
Votado en contra porque es innecesariamente complicado (por ejemplo, usa la reflexión, reinventa la rueda, no tiene en cuenta los diferentes proveedores de bases de datos)
un phu
Votado porque muestra una de las posibles soluciones. Estoy seguro de que ExecuteSqlCommand acepta parámetros de la misma manera que SqlQuery. También me gusta el estilo Dapper.net de pasar los parámetros.
Zar Shardan
0

Parámetros múltiples en un procedimiento almacenado que tiene múltiples parámetros en vb:

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

Los procedimientos almacenados se pueden ejecutar como a continuación

 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
fuente
No te olvides de usar System.Data.SqlClient
FlyingV el
0

Para .NET Core 2.2, puede usarlo FormattableStringpara 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);
Cortacircuitos
fuente
No te olvides de usar System.Data.SqlClient
FlyingV el