【问题标题】:What's the most elegant way to use stored procedures?使用存储过程最优雅的方式是什么?
【发布时间】:2009-03-27 14:20:16
【问题描述】:

我正在我的存储库类中重写内联 SQL 以改用存储过程(安全要求)。在过去使用 Fluent NHibernate 和 Linq2Sql 之后,我发现它非常笨拙和不优雅。

编辑:澄清一下,我不是在寻找适用于存储过程的 ORM 解决方案。我只是想要一些关于编写下面代码的好方法的建议。

有没有什么策略可以让这种代码尽可能优雅?

        string commandText = "dbo.Save";

        using (SqlConnection sql = new SqlConnection(_connString.ConnectionString))
        using (SqlCommand cmd = sql.CreateCommand())
        {
            cmd.CommandText = commandText;
            cmd.CommandType = CommandType.StoredProcedure;

            SqlParameter idParam = new SqlParameter("identity", item.Identity);
            idParam.Direction = ParameterDirection.Input;

            SqlParameter nameParam = new SqlParameter("name", item.Name);
            nameParam.Direction = ParameterDirection.Input;

            SqlParameter descParam = new SqlParameter("desc", item.Description);
            descParam.Direction = ParameterDirection.Input;

            SqlParameter titleParam = new SqlParameter("title", item.)
            descParam.Direction = ParameterDirection.Input;

            //SNIP More parameters

            cmd.Parameters.Add(idParam);
            cmd.Parameters.Add(descParam);
            cmd.Parameters.Add(titleParam);
            //SNIP etc

            sql.Open();

            cmd.ExecuteNonQuery();

            //Get out parameters
        }

        return item;

【问题讨论】:

    标签: c# sql stored-procedures


    【解决方案1】:

    在我们的内部应用程序中,我们通常使用 SqlHelper 类,可在以下链接(下载和说明)中找到该类:http://www.microsoft.com/downloads/details.aspx?familyid=f63d1f0a-9877-4a7b-88ec-0426b48df275&displaylang=en

    从本质上讲,SqlHelper 类消除了声明连接对象、命令等的一些需要,并允许您调用方法来返回对象,例如 DataSet

    然后您可以这样使用 SqlHelper:

    public static int UpdateItem(int parameter1, int parameter2, string parameter3)
        {
            SqlParameter[] arParam = new SqlParameter[3];
            arParam[0] = new SqlParameter("@Parameter1", lotId);
            arParam[1] = new SqlParameter("@Parameter2", saleId);
            arParam[2] = new SqlParameter("@Parameter3", lotNumber);
    
    
            return int.Parse(SqlHelper.ExecuteScalar(connString, CommandType.StoredProcedure, "spName", arParam).ToString(), CultureInfo.InvariantCulture);
        }
    

    希望这会有所帮助:)

    【讨论】:

    • 有传言说 EntLib 将针对 .NET 3.5 进行更新以支持 LINQ。
    • SqlHelper 还将缓存 一些 中的参数,这将节省到数据库的行程。在上面,.NET 将往返于 db 以发现 @Parameter1 等的数据类型。请务必阅读它的用法。
    【解决方案2】:

    获取Enterprise Library 的副本。它是 ADO 的一个很好的包装器。例如:

    using System.Data.Common;
    using System.Globalization;
    using Microsoft.Practices.EnterpriseLibrary.Data;
    
    Database db = DatabaseFactory.CreateDatabase(DatabaseType.MyDatabase.ToString());
    
    using (DbCommand dbCommand = db.GetStoredProcCommand("dbo.MyStoredProc")) {
        db.AddInParameter(dbCommand, "identity", DbType.Int32, item.Identity);
        db.AddInParameter(dbCommand, "name", DbType.String, item.Name);
        db.AddInParameter(dbCommand, "desc", DbType.String, item.Description);
        db.AddInParameter(dbCommand, "title", DbType.String, item.Title);
    
        db.ExecuteNonQuery(dbCommand);
    } // using dbCommand
    

    【讨论】:

      【解决方案3】:

      我通常使用以下示例的一些变体,当然取决于环境:

      我在整个代码中调用的基本辅助方法

      public static SqlCommand CreateStoredProcCmd(string name, SqlConnection con)
      {
          var cmd = new SqlCommand(name, con);
          cmd.CommandType = CommandType.StoredProcedure;
          return cmd;
      }
      
      public static void AddParams(this SqlCommand cmdObject, Params SqlParameter[] parameters)
      {
        foreach(SqlParameter param in parameters)
        {
          cmdObject.Parameters.add(param);
        }
      }
      
      /* Any overloaded methods to create params receiving my param definitions 
      in any manner that the usual new SqlParameter() constructor doesn't handle */
      public static SqlParameter CreateSqlParam(string ParamName,
                                                SqlDbType ParamType,
                                                object value)
      {
          return CreateSqlParam(ParamName, ParamType, ParameterDirection.Input, value);
      }
      
      public static SqlParameter CreateSqlParam(string ParamName, 
                                                SqlDbType ParamType, 
                                                ParameterDirection ParamDir)
      {
          return CreateSqlParam(ParamName, ParamType, ParamDir, null;
      }                          
      
      public static SqlParameter CreateSqlParam(string ParamName, 
                                                SqlDbType ParamType, 
                                                ParameterDirection ParamDir,
                                                object value)
      {
          var parm = new SqlParameter(ParamName, ParamType);
          parm.Direction = ParamDir;
          parm.Value = value;
          return parm;
      }
      

      下面是我如何设置存储过程并优雅地添加所有参数

      public static string DoStuff()
      {
          using (var oCon = new SqlConnection("MyConnectionString"))
          {
              oCon.Open();
              var oCmd = CreateStoredProcCmd("sp_Name", oCon).AddParams(
                  CreateSqlParam("Param1", SqlDBType.Int, 3),
                  CreateSqlParam("Param2", SqlDBType.VarChar, "Hello World"),
                  CreateSqlParam("Param3", SqlDBType.VarChar, ParameterDirection.Output)
              );
              oCmd.Prepare();
              oCmd.ExecuteNonQuery();
              object outVal = oCmd.Parameters["Param3"];
              return null != outVal ? outVal.ToString() : String.Empty;
          }
      }
      

      【讨论】:

        【解决方案4】:

        您可以使用SubSonic 作为您的类和存储过程之间的 ORM 层。这是一个基本的example。 Phil Haack 也有一个很好的 article

        this other question 中有一些很好的信息。

        编辑:由于您已更新问题以表明您不想使用 ORM,因此 SubSonic 不适合您。但是,我会将答案留给其他使用存储过程的人。 :) 你也应该看看你是否有可能使用它。

        【讨论】:

        • 谢谢,我没有看到,但是建议我不要在现有工具之外使用第三方技术,因为它需要得到客户的批准并且需要很长时间。
        • 所以不要告诉他们这是“第三方技术”。您还应该使用此信息更新您的问题。
        • 真的无法摆脱。我去看看它的含义是什么,如果没有,我会把它存入银行以备后用。
        【解决方案5】:

        我建议使用 Microsoft Application Blocks SqlHelper 对象。

        对于您上面列出的声明,我可以执行以下操作。

        SqlHelper.ExecuteNonQuery(_connectionString, "MyProcName", 1, "NameValue", "Description", "Title");
        

        基本上,SQL Helper 需要几个参数。

        1. 连接到数据库的连接字符串
        2. 存储过程的名称
        3. 参数值数组,按它们在存储过程中出现的顺序排列。

        与显式创建每个参数相比,此方法存在非常轻微的性能缺陷,但时间节省通常超​​过它,因为它太小了。

        【讨论】:

        • 真的有性能缺陷吗?编译器不处理这个吗?
        • 我实际上刚刚完成了一系列测试。对于 5 个参数的存储过程,在发出 1 到 1000 条语句时会有 7% 的差异。在 1000 之后,它会下降到大约 4% 左右。一旦我达到大约 20000 个插入,就没有区别了。它似乎与缓存有关。
        • 但实际上考虑到所涉及的时间,您几乎不会注意到单个条目的性能差异为 0.0003 秒。
        【解决方案6】:

        您可以通过从 SqlParameter 派生您自己的 InputSqlParameter 并在构造函数中将方向设置为 Input 来将行数减半。

        那会让你写

            cmd.Parameters.Add(new InputSqlParameter("title", item.title));
            cmd.Parameters.Add(new InputSqlParameter("property", item.property));
        

        这显示了一个模式,让您设置参数名称和项目字段列表,并在 for 循环中添加参数。

        【讨论】:

          【解决方案7】:

          为了使代码不那么冗长,我总是使用添加参数

          cmd.Parameters.AddWithValue("name", item.Name);
          cmd.Parameters.AddWithValue("title", item.Title);
          // and so on
          

          【讨论】:

            【解决方案8】:

            输入是默认方向,你可以缩短参数添加,可能还想声明SqlDBTypes...

            cmd.Parameters.Add("identity", SqlDBType.???).Value = item.Identity;
            cmd.Parameters.Add("desc", SqlDbType.???, ?size?).Value = item.Description;
            cmd.Parameters.Add("title", SqlDBType.???, ?size?).Value = item.Title;
            
            //Output params generally don't need a value so...
            cmd.Parameters.Add("someOut", SqlDBType.???).Direction = ParameterDirection.Output;
            

            【讨论】:

              【解决方案9】:

              将给定存储过程的每个参数保存在“数据类”中。使用注释来指定诸如“in”或“out”存储过程参数之类的东西。

              然后您可以从客户端代码加载类,然后使用反射为存储过程构建所有参数,执行存储过程,并将输出参数加载回数据类。

              稍微干净一点:输入一个类,输出一个类(即使有些是输入/输出)。这样一来(对于客户端代码)就很清楚哪些参数需要在输入的过程中填写,哪些会被返回。这也消除了对这些注释的需要。

              【讨论】:

              • 有趣...我会玩一下
              【解决方案10】:
              using (var conn = new SqlConnection(ConnectionString))
              using (var cmd = conn.CreateCommand())
              {
                  cmd.CommandText = "[dbo].[Save]";
                  cmd.CommandType = CommandType.StoredProcedure;
              
                  cmd.Parameters.Add(new SqlParameter(
                      "Identity", SqlDbType.Int) { Value = item.Identity });
              
                  cmd.Parameters.Add(new SqlParameter(
                      "Name", SqlDbType.NVarChar, 50) { Value = item.Name });
              
                  cmd.Parameters.Add(new SqlParameter(
                      "Title", SqlDbType.NVarChar, 100) { Value = item.Title });
              
                  conn.Open();
                  cmd.ExecuteNonQuery();
              }
              

              这是 Ent Lib 的样子:

              // Note, that you don't need to specify connection string here,
              // it will be automatically taken from a configuration file
              var db = DatabaseFactory.CreateDatabase();
              
              using (var cmd = db.GetStoredProcCommand("[dbo].[Save]"))
              {
                  db.AddInParameter(cmd, "Identity", DbType.Int32, item.Identity);
                  db.AddInParameter(cmd, "Name", DbType.String, item.Name);
                  db.AddInParameter(cmd, "Title", DbType.String, item.Title);
                  db.ExecuteNonQuery(cmd);
              }
              

              您还可以使用 Enterprise Library 中的 SqlHelper 方法来简化此语法。

              SqlHelper.ExecuteNonQuery(connectinString,
                   CommandType.StoredProcedure, "[dbo].[Save]", new SqlParameter[]
                       {
                           new SqlParameter("Identity", item.Identity),
                           new SqlParameter("Name", item.Name),
                           new SqlParameter("Title", item.Title)
                       }); 
              

              【讨论】:

              • 如果使用“using”,不需要尝试finally,它会自动调用dispose,它本身会调用close
              猜你喜欢
              • 1970-01-01
              • 2023-03-30
              • 1970-01-01
              • 2010-09-21
              • 1970-01-01
              • 1970-01-01
              • 2012-08-08
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多