使用带有connection.open的using语句

16

我在看一些代码,并与同事们讨论。

具体地说,是这样的一段代码。

    [Test]
    public void TestNormalWay()
    {
        using(var cn = GetConnection())
        {
            cn.Open();
            // do stuff
        }
    }

有人问:

"为什么不把cn.Open放到GetConnection方法中呢?"

我的回答是,如果"Open"抛出异常,Dispose将不会被调用。他的回应是

"那怎样?连接没有打开,为什么需要关闭(或Dispose)呢?"

对我来说,这只是不想知道是否需要Dispose/Close的问题。因此,我会在代码中重复cn.Open,而不是将其移动到共享函数中。

但是这很有趣……所以我在SQL Server Connection Pooling (ADO.NET)上进行了一些阅读。

对我来说,如果cn.Open引发异常,似乎不存在需要调用Dispose的情况。

因此,在下面的例子中,“TestNormalWay”和“WhyNotDoItThisWay”之间真的有任何区别吗?

    protected static DbConnection GetConnection()
    {
        DbConnection cn = new SqlConnection("SomeConnecitonstring... ");
        return cn; 
    }

    protected static DbConnection GetConnectionDangerousVersion()
    {
        DbConnection cn = new SqlConnection("SomeConnecitonstring... ");
        cn.Open();  // this will throw.. .dispose not called
        return cn; 
    }

    [Test]
    public void TestNormalWay()
    {
        using(var cn = GetConnection())
        {
            cn.Open();
            // do stuff
        }
    }

    [Test]
    public void WhyNotDoItThisWay()
    {
        using(var cn = GetConnectionDangerousVersion())
        {
            // do stuff
        }
    }

所以根据你的说法和我所看到的,Dispose方法只有在WhyNotDoItThisWay方法中被调用的唯一方式是:仅仅因为你调用Open并不会自动Dispose连接,这个道理你明白吧。将变量cn包装在using(){}中,cn会自动被处理掉,同时也假设你实例化了该对象。 - MethodMan
如果您正在使用 using 语句,您不应该自己处理对象的释放。正如您的同事所怀疑的那样,如果在尝试打开连接时发生异常,则对象不包含任何实际可处理的信息。此外,如Servy所建议的,如果您真的想要安全起见,可以简单地使用 try()catch()finally() 块。 - Security Hound
4个回答

7
您编写代码时,总是希望在创建连接后立即打开它,因此没有区别。
但是,在设计用于这样做的代码中,可以多次打开和关闭连接,并且有很大的区别。
我可能想编写一些代码,其中包含一个长时间运行的例程,需要一个连接对象,并随着时间的推移而打开和关闭它。该例程可能不关心连接对象是如何创建的。因此,将创建连接操作与打开和关闭操作分离是一个优点。
至于资源管理问题,我同意这不是一个问题。仅创建SQL连接对象本身并不会锁定任何资源,打开它才会获取池化连接。如果打开操作返回异常,我认为可以合理地假定连接未被打开。

1
你可以这样做,但在我看来这不是一个好的API设计。API应该是最小化的,提供像这样的复合方法只是为了减少打字感觉对我来说是一种丑陋的API设计方式。但如果你想要一个像那样的连接对象,你可以编写一个包装器。 - James Gaunt
MSDN上说:“当请求SqlConnection对象时,如果有可用的连接,则从池中获取。”这是指打开连接还是“new”连接字符串时创建它。 - John Sobolewski
1
这篇文章有些令人困惑,因为在第三段中它说“每当用户调用连接的Open方法时,池化程序会在池中查找可用的连接”,这与jsobo发现的那行代码相矛盾。 - Evan M
仅仅因为调用open方法抛出了异常并且连接没有被“打开”,并不意味着没有一些需要被处理的资源。我认为这个答案是推测性的,不是基于事实的,换句话说是“观点”,所以我不会将其标记为正确答案。 - John Sobolewski
如果从“open”中收到错误消息会发生什么呢?我认为你是正确的,没有任何需要处理的东西。因此,将打开放在using {}中可能是很好的风格,但只要确保已经“打开”的连接实际上被处理掉了,这可能并不重要。 - John Sobolewski
显示剩余4条评论

7
我倾向于在你的方法中仅返回SqlConnection的实例,而不调用Open()。如果需要打开连接,则应在需要时进行。在实用函数中不需要打开连接。
原因之一是有些对象需要一个SqlConnection,但并不一定需要打开它们。例如,SqlDataAdapter需要SqlConnection,并在内部处理其开放和关闭。当然,您可以在传递之前打开连接,但那么您必须明确关闭连接。
回到几步,调用代码应负责处理SqlConnection的具体操作。

这是最安全的选择,因为不确定何时以及是否需要调用dispose,这将确保它始终被调用(除非有人拔掉电源线等情况...) - John Sobolewski
我重新阅读了整篇 MSDN 文章,并发现了这个内容:“我们强烈建议您在使用完连接后始终关闭连接,以便将连接返回到池中。您可以使用 Connection 对象的 Close 或 Dispose 方法之一来执行此操作,或者在 C# 中打开所有连接时使用 using 语句”……再次出现了另一个含糊不清的陈述。它没有指定您实际上是否调用了连接的 open!只是您使用了它。 - John Sobolewski

1
您可以在第二个“危险”版本的 .Open() 调用周围加上 try/catch,这样如果打开时抛出异常,您仍然可以释放连接。

这样做不会在返回连接之前释放它吗...那还有什么意义呢? - John Sobolewski
@jsobo 你是正确的。应该使用try/catch,而不是try finally。已经修改。 - Servy

0

在查看了SqlConnection的内部后,我基本上相信这并不重要。一个快速的测试似乎证实了这一点:

static void Main( string[] args )
{
   Func<SqlConnection> getConnection =
            () =>
               {
                  var connection =
                     new SqlConnection(
                        "Initial Catalog=myDatabase;Server=(local);Username=bogus;password=blah;Connect Timeout=10;" );

                  connection.Open();
                  return connection;
               };

   while(true)
   {
      try
      {
         using( var connection = getConnection() )
         {
            var cmd = new SqlCommand( "SELECT 1", connection ) {CommandType = CommandType.Text};
            cmd.ExecuteNonQuery();
         }
      }
      catch ( Exception )
      {
         // ignore exception
      }
   }
}

我用分析器附加了一段时间来运行这段代码。它不仅运行得非常快,而且也没有泄漏任何内存。这基本上让我相信在你的GetConnection方法中打开连接是可以的。

当然,这里发布的所有其他论点仍然有效;如果您立即要使用它,您应该只打开连接。


这并不一定证明什么... 如果连接有时打开成功而有时失败... 这会导致创建一个连接池,然后检索资源,但没有处理吗? 例如... 打开实际上没有打开,但在背后尝试进行连接重置? - John Sobolewski
@jsobo 好的,你会怎样进行测试?我尝试了不同组合的工作和非工作连接字符串以及不同类型的错误。据我所知,每当 SqlConnection.Open 抛出异常时,它都会清理任何潜在的资源。 - Marnix van Valen
我不知道...有很多情况是你无法测试的。问题实际上取决于你何时得到必须处理的东西...是在NEW的时候还是OPEN的时候。如果你new...并且从池中获取了一些东西,那么打开会重置现有连接并抛出异常...那么你最好调用dispose...如果它在幕后从池中检索并重置(只在.Open时)并抛出异常,那么你从未从池中获取连接,就不必处理... - John Sobolewski
我完全同意James Gaunt的观点。通过查看源代码(使用反编译工具)和一些实证测试,我可以说他是正确的,创建连接并不占用任何资源。只有在成功打开连接后,才需要关闭它。 - Marnix van Valen

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