【问题标题】:How to pass parameters to the DbContext.Database.ExecuteSqlCommand method?如何将参数传递给 DbContext.Database.ExecuteSqlCommand 方法?
【发布时间】:2011-07-25 08:39:14
【问题描述】:

假设我有在实体框架中直接执行 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 中那样传入命名参数,documentation for this method 没有提供任何有关如何执行参数化查询的示例。

如何正确指定参数?

【问题讨论】:

    标签: entity-framework entity-framework-4.1


    【解决方案1】:

    对于 .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
    【解决方案2】:

    存储过程可以像下面这样执行

     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
    【解决方案3】:

    对于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 Injection 的影响,并且不会产生任何参数。

    请参阅this 了解更多信息。

    【讨论】:

    • 我非常喜欢 .NET Core 2.0,它让我喜极而泣 :')
    • 我可以看到一些人的热情,因为到目前为止 .NET Core 2.0 对我来说是一个更顺畅的旅程。
    • 第一个如何不易被SQL注入攻击?两者都使用 C# 字符串插值。据我所知,第一个无法抢占字符串扩展。我怀疑你的意思是ctx.Database.ExecuteSqlCommand("Update [User] SET FirstName = {firstName} WHERE Id = {id}", firstName, id);
    • @CodeNaked,不,我不是那个意思。 EF 意识到了这个问题,并创建了两个实际参数来保护您。所以它不仅仅是一个被传递的字符串。有关详细信息,请参阅上面的链接。如果你在VS中尝试,我的版本不会发出Sql Injection的警告,而另一个会。
    • @GregGum - 关于FormattableString。你是对的,这很酷!
    【解决方案4】:

    vb中有多个参数的存储过程中的多个参数:

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

    【讨论】:

      【解决方案5】:
      var firstName = "John";
      var id = 12;
      
      ctx.Database.ExecuteSqlCommand(@"Update [User] SET FirstName = {0} WHERE Id = {1}"
      , new object[]{ firstName, id });
      

      就是这么简单!!!

      了解参数参考图片

      【讨论】:

        【解决方案6】:

        如果您的底层数据库数据类型是 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 来查看差异。

        【讨论】:

          【解决方案7】:

          您可以:

          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 等)。

          【讨论】:

          • 赞成提到 {0} 语法与数据库无关。 “...您不需要[原文]使用工厂或知道要创建什么类型的 DbParamaters[原文]...”
          • 场景 1 已被弃用,取而代之的是插值版本。现在等效为: DbContext.Database.ExecuteSqlInterpolated($"StoredProcedureName {paramName}");
          【解决方案8】:

          事实证明这是可行的。

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

          【讨论】:

          • 这会起作用,但是这种机制允许 SQL 注入,并且还可以防止数据库在语句再次出现但具有不同值时重用执行计划。
          • @GregB 我不认为你在这里是正确的。例如,我已经测试过它不允许我提前终止字符串文字。此外,我查看了源代码,发现它使用 DbCommand.CreateParameter 将任何原始值包装成参数。所以没有 SQL 注入,而且是一个简洁的方法调用。
          • @JoshGallagher 是的,你是对的。我在想一个 string.Format 场景把它放在一起。
          • 它在 SQL Server 2008 R2 中不起作用。您将拥有@p0、@p2、...、@pN,而不是您传递的参数。请改用SqlParameter("@paramName", value)
          • @GregBiles 和其他人:这个重载绝对不是使用字符串格式,即使它看起来很像。如果你在 profiler 中查看 sql,它是参数化的。在这种情况下,.NET 正在做正确的事情,将 {0} 更改为 `@p0,然后进行参数化。这是一个权衡。它确实看起来像邪恶的 string.format,但它比必须执行 new SqlParameter("@foo",paramvalue) 更简洁(并且与 db 无关,请参见下面的其他答案)
          【解决方案9】:

          对于异步方法(“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-agnostic,它只适用于 MS-SQL Server。对于 Oracle 或 PG,它将失败。
          【解决方案10】:

          Oracle 的简化版本。如果不想创建 OracleParameter

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

          【讨论】:

          • 在 SQL Server 中,我使用 @p0 而不是 :p0。
          【解决方案11】:

          试试这个:

          var sql = @"Update [User] SET FirstName = @FirstName WHERE Id = @Id";
          
          ctx.Database.ExecuteSqlCommand(
              sql,
              new SqlParameter("@FirstName", firstname),
              new SqlParameter("@Id", id));
          

          【讨论】:

          • 这确实应该是正确的答案,上面那个容易受到攻击,不是最佳实践。
          • @Min,接受的答案并不比这个答案更容易受到攻击。也许您认为它使用的是 string.Format - 它不是。
          • 这应该被标记为答案!这是唯一正确的答案,因为它适用于 DateTime。
          【解决方案12】:

          其他答案在使用 Oracle 时不起作用。您需要使用: 而不是@

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

          【讨论】:

          • 谢天谢地,没有人使用 Oracle。好吧不是自愿的!编辑:为迟到的笑话道歉!编辑:为这个坏笑话道歉!
          【解决方案13】:
          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();
          

          【讨论】:

          • 您能否解释一下这段代码以及为什么它是对所提出问题的回答,以便以后找到它的人能够理解它?
          • {0} 语法的问题会导致您失去可读性 - 我个人不太喜欢它。传递需要指定 DbParameter(SqlParameter、OracleParameter 等)的具体实现的 SqlParameters 的问题。提供的示例可让您避免这些问题。
          • 我想这是一个有效的意见,但您还没有在这里回答实际上被问到的问题;如何将参数传递给ExecuteSqlCommand() 您应该确保在发布答案时回答所提出的具体问题。
          • 投反对票,因为它过于复杂(例如使用反射、重新发明轮子、无法考虑不同的数据库提供者)
          • 赞成,因为它显示了一种可能的解决方案。我确定 ExecuteSqlCommand 接受参数的方式与 SqlQuery 相同。我也喜欢 Dapper.net 传递参数的风格。
          【解决方案14】:

          试试这个(已编辑):

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

          之前的想法是错误的。

          【讨论】:

          • 当我这样做时,我收到以下错误:“不存在从对象类型 System.Data.Objects.ObjectParameter 到已知托管提供程序本机类型的映射。”
          • 对不起我的错误。使用 DbParameter。
          • DbParameter 是抽象的。您必须使用 SqlParameter 或使用 DbFactory 来创建 DbParameter。
          猜你喜欢
          • 2011-07-18
          • 1970-01-01
          • 2021-12-13
          • 2012-02-23
          • 2023-04-05
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多