【问题标题】:SqlConnection SqlCommand SqlDataReader IDisposableSqlConnection SqlCommand SqlDataReader IDisposable
【发布时间】:2013-06-03 20:46:15
【问题描述】:

SqlConnectionSqlCommandSqlDataReader 都实现了IDisposable 接口。我读到了始终将IDisposables 包装到using 块中的最佳实践。

所以,我查询数据的常见场景看起来像这样(在更大的上下文中,像 linq2sql 这样的映射工具当然是合适的,但假设我们想在这里使用这种方法):

using (SqlConnection cn = new SqlConnection("myConnectionstring"))
{
    using (SqlCommand cm = new SqlCommand("myQuery", cn))
    {
        // maybe add sql parameters
        using (SqlDataReader reader = cm.ExecuteReader())
        {
             // read values from reader object
             return myReadValues;
        }
    }
}

这是正确的方法还是被认为是矫枉过正?我有点不确定这种级别的嵌套using 块,但我当然想以正确的方式来做。 谢谢!

【问题讨论】:

    标签: c# .net sql-server ado.net


    【解决方案1】:

    这是 100% 正确的方法。如果一个类利用了IDisposable,它应该被包装在一个using 语句中,以确保调用Dispose() 方法。此外,不应掉以轻心,与诸如 SQL Server 之类的不受管理的外部技术进行通信。 SqlCommand 对象实现 IDisposable 是有充分理由的。下面的代码是SqlCommand对象的Dispose()方法:

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            this._cachedMetaData = null;
        }
        base.Dispose(disposing);
    }
    

    如您所见,它正在释放对 _cachedMetaData 对象的引用,以便清理它。

    【讨论】:

    • 感谢您的回答,尤其是关于 SqlCommands 的处置的详细信息 - 正是我正在寻找的信息!
    • @Chips_100,很高兴能为您提供帮助!
    • 仅供参考,“使用”是尝试的语法糖 --> finally 块在 finally 调用 Dispose 的地方。但是不要那样做 - 使用 using 关键字。 :)
    • "它正在释放对 _cachedMetaData 对象的引用" - 但在 OP 的示例中,一旦包含 SqlCommand 的 _cachedMetaData 对象超出范围,它就会有资格进行 GC ,没有using 语句可能比使用它更快。注意我并不是反对调用 Dispose:最好不要依赖SqlCommand 的内部知识,而只是盲目地遵循模式。
    【解决方案2】:

    您可以使用以下排版方式使代码更靠近左侧:

    using (SqlConnection cn = new SqlConnection("myConnectionstring"))
    using (SqlCommand cm = new SqlCommand("myQuery", cn))
    using (SqlDataReader reader = cm.ExecuteReader())
    {
         // read values from reader object
         return myReadValues;
    }
    

    正如其他人已经指出的那样,使用三个嵌套的 using 块是正确的。

    【讨论】:

    • 如果稍后您自动识别其余代码(即:Visual Studio 中的 Ctrl + K + D),您将失去它。
    • 谢谢,我以前不知道“堆叠”using 声明。这是一件好事,虽然我可以不使用它,如果我需要向 SqlCommand 添加参数。再次感谢您的信息!
    • @Renan 实际上我在 VS2012 中不会丢失它。如果我没记错的话,VS2010 也确实支持这种“使用堆栈”...
    • 我认为这段代码不起作用,因为连接从未打开。像这样堆叠看起来不错,但它只是不可行,因为总是需要做一些事情(cn.Open()、cm.Parameters.AddWithValue() 等)
    【解决方案3】:

    这是正确的方法,如果您将与读者一起完成。有时,您需要阅读器保持打开状态(也许您的方法返回它),因此立即处理阅读器是行不通的。在这些情况下,ExecuteReader 的重载可以帮助您:

    var cn = new SqlConnection("myConnectionstring");
    var cm = new SqlCommand("myQuery", cn);
    var reader = cm.ExecuteReader(CommandBehavior.CloseConnection);
    return reader;
    

    这将保持连接和阅读器打开。一旦阅读器关闭/处置,它也会关闭(并处置)连接。

    using(var reader = GetReader()) //which includes the code above
    {
       ...
    } // reader is disposed, and so is the connection.
    

    【讨论】:

    • 您的GetReader 示例存在缺陷,因为如果new SqlCommandExecuteReader 抛出,它不会处理连接。请参阅以下答案以获得更完整的实现:stackoverflow.com/a/744307/13087
    • @Joe 好点,这不是一个完整的实现(感谢链接)。我想指出的主要一点是当读者需要保持打开状态时使用CommandBehavior.CloseConnection
    【解决方案4】:

    是的,这是正确的。您可能会错过嵌套使用中的大括号,因为它们是一个语句,但我认为这不会增加可读性。

    【讨论】:

      【解决方案5】:

      这并不过分。 using 块是一种很好的做法,因为它可以确保调用对象的 Dispose() 方法,即使抛出异常也是如此。

      不过,这种东西是有名字的。它被称为代码糖。所以:

      using (foo bar = new foo()) { //...snip }
      

      是以下的简写代码:

      foo bar = null;
      Exception error = null;
      try {
          bar = new foo();
          // ...snip
      }
      catch (Exception ex) {
          error = ex;
      }
      finally {
          if (bar != null) bar.Dispose();
          if (error != null) throw error;
      }
      

      任何一种形式都等同于另一种形式,它们只是写同一件事的不同方式。换句话说,forwhile 之间的区别相同:它们的作用基本相同,但使用方式不同。

      using 是首选,因为它使代码更短且更具可读性,并为您自动处理。不过,至于你是否应该使用它,不要听那些说你应该总是做某事的人。这是很好的做法,当然,但知道何时使用为什么使用以及使用或不使用的好处和后果是值得的不仅仅是因为人们说你应该做某事

      编辑: Eren 的答案有一个示例,说明您不希望为 reader 设置一个 using 块。

      【讨论】:

        【解决方案6】:

        我认为处置SqlCommand 没有任何意义。 SqlConnectionSqlDataReader 应该被丢弃。

        SqlConnection,因为它在超出范围时不会自行关闭。

        SqlDataReader 可以让SqlConnection 保持忙碌,直到阅读器关闭。

        甚至 MSDN 示例都没有处理 SqlCommand

        【讨论】:

        • 嗯?它实现了 Idisposable,所以它会被处理掉。为什么要让它漂浮,直到(如果)GC 启动。
        • 处置任何实现IDisposable 的类实例被广泛接受为最佳实践。问题不是为什么要处理 SqlCommand,而是为什么不处理?
        • 我只是指出,这实际上并不比处理任何不重要的东西更重要。 @TonyHopkinson,您是否在创建的每个对象超出范围后运行 GC?不?因为没有意义。 Dispose 还使用时钟周期。您应该总体上反对垃圾收集,或者根本不反对。
        • 有 SqlCommand、DataTable 等实现 IDisposable 的类,但它们所做的只是将私有引用设置为 null,如果包含的实例超出范围,这将无效。我通常会 dispose SqlCommand,但严格来说这个答案是正确的,省略 dispose 不会产生不利影响。
        • 同意 Evan Lewis 的观点,不,我不运行 GC,除非在极端情况下。记住 Dispose 比记住几个你可以侥幸逃脱的情况要重要得多。更不用说它给你的固有范围了..
        【解决方案7】:

        我不是专家,但我知道 using 被转换为 try/finally 块,也许您可​​以将 SqlConnection、SqlCommand 和 SqlDataReader 包装成一个独特的 try/finally

        try {
             // code here with SqlConnection, SqlCommand and SqlDataReader
        }
        finally
        {
          // Dispose call on SqlConnection, SqlCommand and SqlDataReader
         }
        

        【讨论】:

        • 不是我标记了你,但不要尝试这个。它不是等效的,如果发生异常会像筛子一样泄漏并可能引发更多异常,并且应该从编译器中引发一堆警告。
        • @TonyHopkinson 我回答只是为了获得反馈。你能进一步解释为什么它不等价吗?非常感谢!
        • 如果 sql 连接连接引发异常。最后执行,然后您在 sqlcommand 上得到一个空引用异常。为了解决这个问题,您必须测试 nil。
        猜你喜欢
        • 2011-01-06
        • 2012-01-12
        • 2013-02-06
        • 1970-01-01
        • 2018-03-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-23
        相关资源
        最近更新 更多