如何将参数传递给DbContext.Database.ExecuteSqlCommand方法?


222

假设我确实有必要在实体框架中直接执行sql命令。我在弄清楚如何在我的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中一样传递命名参数,并且该方法文档没有提供有关如何执行参数化查询的任何示例。

如何正确指定参数?

Answers:


294

试试这个:

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

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

2
这确实应该是标记为正确的答案,上面的一个容易受到攻击,不是最佳实践。
2014年

8
@Min,可接受的答案不比该答案更容易受到攻击。也许您认为它使用的是string.Format-并非如此。
SimonMᶜKenzie2014年

1
这应该标记为答案!这是唯一的正确答案,因为它与DateTime一起使用。
2015年

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注入,并且还可以防止当语句再次出现但值不同时数据库重新使用执行计划。
Greg Biles

95
@GregB我认为您在这里不正确。我已经测试过,例如,它不允许我提前终止字符串文字。此外,我查看了源代码,发现它正在使用DbCommand.CreateParameter将所有原始值包装到参数中。因此,无需进行SQL注入,并且可以调用简洁的方法。
Josh Gallagher

7
@JoshGallagher是的,您是对的。我在想一个string.Format场景,将它们放在一起。
Greg Biles

6
从SQL Server 2008 R2开始它不起作用。您将拥有@ p0,@ p2,...,@ pN,而不是您传递的参数。使用SqlParameter("@paramName", value)代替。
Arnthor

如果将数据库表列指定为ANSI varchar,则无法相信没有人提到此查询的性能影响。.Net字符串是unicode,意味着参数将作为nvarchar传递到服务器。这将导致严重的性能问题,因为数据层必须执行数据转换。您应该坚持使用SqlParameter的方法并指定数据类型。
AKD

68

您可以:

1)传递原始参数,并使用{0}语法。例如:

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

2)传递DbParameter子类参数,并使用@ParamName语法。

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

如果使用第一种语法,则EF实际上将用DbParamater类包装参数,为其分配名称,并将{0}替换为生成的参数名称。

首选第一种语法,因为您不需要使用工厂或知道要创建哪种类型的DbParamater(SqlParameter,OracleParamter等)。


6
赞成提及{0}语法与数据库无关。“ ...您不需要使用工厂或知道要创建什么类型的DbParamaters ...”
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 2011年

抱歉,是我的错。使用DbParameter。
Ladislav Mrnka'3

7
DbParameter是抽象的。您必须使用SqlParameter或使用DbFactory创建一个DbParameter。
jrummell 2011年

12

对于实体Framework Core 2.0或更高版本,执行此操作的正确方法是:

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

请注意,实体框架将为您生成两个参数,因此您可以免受Sql Injection的攻击。

另请注意,它不是:

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

因为这不能保护您免受Sql Injection的影响,并且不生成任何参数。

更多。


3
我非常喜欢.NET Core 2.0,它让我流下了喜悦的泪水:')
Joshua Kemmerer

我可以看到一些人的热情,因为到目前为止,.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的警告,而另一个会。
Greg Gum

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。
Marek Malczewski,

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,它将失败。
ANeves

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事件探查器以了解区别。


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
您是否有机会解释此代码,以及为什么它可以解决所提出的问题,以便以后来查找此代码的人可以理解它?
Andrew Barber 2012年

1
{0}语法的问题是您的可读性很差-我个人不太喜欢它。传递SqlParameters时需要指定DbParameter的具体实现(SqlParameter,OracleParameter等)的问题。提供的示例使您可以避免这些问题。
Neco

3
我想这是一个有效的意见,但您尚未回答此处实际提出的问题;如何将参数传递给ExecuteSqlCommand()您在发布答案时,应确保回答所询问的特定问题。
安德鲁·巴伯

3
被否决的原因是它不必要地复杂(例如,使用反射,重新发明轮子,无法考虑其他数据库提供者)
2015年

投票最多,因为它显示了可能的解决方案之一。我确定ExecuteSqlCommand接受参数的方式与SqlQuery相同。我也喜欢Dapper.net传递参数的样式。
Zar Shardan

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.