SqlConnection SqlCommand SqlDataReader IDisposable SqlConnection是一个用于连接到SQL Server数据库的类。SqlCommand是用于向数据库发送SQL语句并执行它们的类。SqlDataReader是一种可读取行数据的轻量级和高性能方式。IDisposable是一个接口,用于释放非托管资源。

19

SqlConnectionSqlCommandSqlDataReader都实现了IDisposable接口。我了解到一种最佳实践是始终将IDisposable对象包装在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 块有点不确定,但当然我希望按照正确的方法去做。 谢谢!

8个回答

16

这绝对是正确的做法。如果一个类使用了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的dispose的详细说明 - 这正是我在寻找的信息! - Dennis
@Chips_100,我很高兴能够提供帮助! - Mike Perrenoud
只是提醒一下,"using" 是一个语法糖,用于将 try --> finally 块转换为 finally 调用 Dispose。不过不要那样做 - 使用 using 关键字。 :) - Haney
“它正在释放对_cachedMetaData对象的引用” - 但在OP的示例中,当包含SqlCommand的作用域结束时,_cachedMetaData对象将变得可回收,而没有using语句可能比有using语句更早。请注意,我并不是在反对调用Dispose:最好不要依赖于对SqlCommand内部的了解,而是盲目地遵循模式。 - Joe

5
您可以使用以下排版方式将代码靠近左侧:
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),则会丢失该设置。 - Geeky Guy
谢谢,我之前不知道可以使用“堆叠”using声明。这是一个很好的功能,尽管如果我需要向SqlCommand添加参数,我可能无法使用它。再次感谢您提供的信息! - Dennis
1
@Renan 实际上我在VS2012中没有失去它。如果我没记错的话,VS2010也支持这个“使用堆栈”的功能... - Matthias Meid
4
我认为这段代码无法正常工作,因为连接从未被打开。虽然堆叠代码看起来很好,但并不可行,因为总有需要完成的任务(比如cn.Open()、cm.Parameters.AddWithValue()等)。 - Tobberoth

4

如果你需要保持读取器打开状态(例如,你的方法返回了它),那么直接释放读取器是行不通的。在这种情况下,可以使用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 抛出异常时,它不会释放连接。请参考以下答案以获取更完整的实现:https://dev59.com/t3RB5IYBdhLWcg3wCjYV#744307 - Joe
@Joe 很好的观点,这并不是一个完整的实现(感谢链接)。我想要指出的主要是当读取器需要保持打开状态时使用 CommandBehavior.CloseConnection - Eren Ersönmez

2

这并不是过度的操作。使用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块的情况。

1

没错,嵌套使用时可以省略大括号,因为它们是一个语句,但我认为这并不有助于可读性。


-1

我认为释放 SqlCommand 没有任何意义。但是,SqlConnectionSqlDataReader 应该被释放。

SqlConnection 不会在超出范围时自动关闭。

SqlDataReader 可以使 SqlConnection 保持繁忙状态,直到读取器被关闭。

即使是 MSDN 的示例也没有释放 SqlCommand


什么?它实现了IDisposable接口,所以它将被处理掉。你为什么要让它漂浮不定,直到(如果)垃圾回收启动呢? - Tony Hopkinson
将实现“IDisposable”的任何类实例处理掉被广泛认为是最佳实践。问题不在于为什么要处理SqlCommand,而在于为什么不处理呢? - Evan L
我只是想指出,这并不比处理其他非关键事项更重要。@TonyHopkinson,每当您创建的对象超出范围时,您是否都运行GC? 不是吗? 因为没有意义。Dispose也会使用时钟周期。 您应该要么反对垃圾回收,要么根本不反对。 - SamiHuutoniemi
有一些类,比如SqlCommand、DataTable实现了IDisposable接口,但它们所做的只是将一个私有引用设置为null,如果包含它们的实例本来就要超出作用域,这样做也没有任何影响。正常情况下我会释放SqlCommand的资源,但严格来说,这个答案是正确的,省略dispose调用不会产生负面影响。 - Joe
1
同意Evan Lewis的观点,我也不运行GC,除非在极端情况下。记得Dispose比记住一些可以不这样做的情况更重要。更不用说它给你带来的内在作用域了。 - Tony Hopkinson

-2

我不是专家,但我知道使用可以被翻译成try/finally块,也许你可以将SqlConnection、SqlCommand和SqlDataReader包装到一个唯一的try/finally中。

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

不是我给你打了分数,但不要尝试这个。它不等同,并且在异常情况下会像漏斗一样泄漏,可能会引发更多的异常并应该从编译器中引发一堆警告。 - Tony Hopkinson
@TonyHopkinson 我回答只是为了得到反馈。您能进一步解释为什么它不等效吗?非常感谢! - Davide Lettieri
1
如果 SQL 连接引发异常,则最终将执行,然后在 SQL 命令上获取空引用异常。为了解决这个问题,您必须测试是否为空。 - Tony Hopkinson

-2

SqlConnection未被识别为IDisposable 问题描述

开发人员在尝试在using语句中使用SqlConnection时遇到了一个错误,即SqlConnection未被识别为IDisposable。这个问题会阻碍正确的数据库连接,并且对于试图在他们的ASP.NET Core应用程序中与SQL Server数据库进行交互的开发人员来说可能会很令人沮丧。 问题症状

Error message: "type used in a using statement must be implicitly convertible to 'System.IDisposable'."
SqlConnection methods like Open() and Close() are not recognized.

根本原因
问题发生在使用的命名空间存在冲突或混淆时。使用System.Data.SqlClient;指令可能与另一个SqlConnection类发生冲突,导致错误。 解决方案
为了解决这个问题,需要明确指定完整的命名空间来使用SqlConnection的完全限定名称:
System.Data.SqlClient.SqlConnection myCon = new System.Data.SqlClient.SqlConnection(sqlDatasource);

通过使用完全限定名,我们确保使用正确的SqlConnection类,解决命名空间冲突,并允许SqlConnection对象被识别为IDisposable。 实施解决方案的步骤
Replace using System.Data.SqlClient; with using System.Data.SqlClient; in the code file.
Update the SqlConnection declaration to use the fully qualified name:
 

System.Data.SqlClient.SqlConnection myCon = new System.Data.SqlClient.SqlConnection(sqlDatasource);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接