当出现异常时,如何确保SQL连接被正确关闭?

18

我经常使用类似这样的模式。我想知道这样做是否正确,或者是否有一种最佳实践我没有应用到。

具体来说,如果抛出异常,在finally块中的代码是否足以确保适当关闭连接?

public class SomeDataClass : IDisposable
{
    private SqlConnection _conn;

    //constructors and methods

    private DoSomethingWithTheSqlConnection()
    {
        //some code excluded for brevity

        try
        {
            using (SqlCommand cmd = new SqlCommand(SqlQuery.CountSomething, _SqlConnection))
            {
                _SqlConnection.Open();
                countOfSomething = Convert.ToInt32(cmd.ExecuteScalar());
            }
        }
        finally
        {
            //is this the best way?
            if (_SqlConnection.State == ConnectionState.Closed)
                _SqlConnection.Close();
        }

        //some code excluded for brevity
    }

    public Dispose()
    {
        _conn.Dispose();
    }
}

1
嗯...为什么在关闭连接之前要检查它是否已经关闭?如果你只在使用它的唯一方法中关闭它,为什么要使用类成员来存储连接呢? - Shog9
1
spoon16包含了“//为简洁起见省略了一些代码”的短语。由此推断,这不是唯一使用它的方法。 - Jeffrey L Whitledge
正确,这绝对是我能想到的最简单的例子。由于连接池的存在,我认为我不需要尝试在每个方法的范围之外存储一个 SqlConnection 对象。因此,只需像其他人建议的那样使用 USING。 - Eric Schoonover
关于“Finally”部分(尽管已经指出它根本不必要),您需要检查对象是否为空。您不能在空对象上调用.close。 - Andrew Harry
9个回答

46

将您的数据库处理代码包装在 "using" 语句中

using (SqlConnection conn = new SqlConnection (...))
{
    // Whatever happens in here, the connection is 
    // disposed of (closed) at the end.
}

还要注意,除非您在连接字符串中明确关闭它,否则.NET默认情况下会池化连接,因此您不需要尝试保留连接以重用它。 - Adam Hughes
在 using 块内部明确抛出异常时,连接和 cmd 对象是否仍然关闭? - Robertcode

8
.Net框架保持连接池是有原因的,请相信它! :) 您不必编写大量代码来连接数据库和释放连接。
您只需使用“using”语句,并确信“IDBConnection.Release()”会为您关闭连接。
过于复杂的“解决方案”往往会导致错误的代码。简单就是更好的选择。

6

MSDN文档已经非常清晰了...

  • 调用Close方法会回滚任何未完成事务。然后将连接放回连接池,如果已禁用连接池,则关闭连接。

您可能没有(也不想)禁用连接池,因此池最终在调用“Close”后管理连接的状态。这很重要,因为您可能会对数据库服务器端的所有打开连接感到困惑。


  • 应用程序可以多次调用Close方法,不会生成异常。

那么为什么要测试Closed呢? 只需调用Close()即可。


  • Close和Dispose具有相同的功能。

这就是为什么using块会导致连接关闭。 using为您调用Dispose。


  • 不要在类的Finalize方法中调用Connection、DataReader或任何其他托管对象上的Close或Dispose。

重要的安全提示。谢谢Egon。


2
我猜你是想说当 _SqlConnection.State == ConnectionState.Closed 时应该用 !=。这样做肯定可以。如果你想要重复使用同一个连接对象,那么你现在的代码就很好了,但更常规的方法是将连接对象本身放在 using 语句中。然而,你需要改变的一件事是 Dispose() 方法。在 dispose 中不应引用连接对象,因为此时它可能已经被终结。你应该按照推荐的 Dispose 模式进行操作。

1

既然您已经在使用IDisposables,那么可以使用'using'关键字,它基本上相当于在finally块中调用dispose,但看起来更好。


1

将连接关闭代码放在 "Finally" 块中,就像你所展示的那样。"Finally" 块会在异常抛出之前执行。使用 "using" 块同样有效,但我发现显式的 "Finally" 方法更清晰。

"Using" 语句对许多开发人员来说已经很老套了,但年轻的开发人员可能不知道这个技巧。


“using”语句对于熟悉C#的人来说非常清晰。目前我专业使用Java,与“using”相比,try/finally的额外负担确实很繁琐。“using”语句是C#中处理资源的惯用方式。 - Jon Skeet
好的,但我发现即使我知道"using"这样的东西是做什么的,实际看到代码也有助于我可视化操作。此外,查看我的代码的人不只有我一个。 - Ed Schwehm

1

请参考以下问题的答案:

关闭和处理 - 应该调用哪个?

如果您的连接生命周期是单个方法调用,请使用语言的using功能来确保正确清理连接。虽然try/finally块在功能上相同,但需要更多的代码,而且在我看来不够可读。无需检查连接的状态,您可以随时调用Dispose,它将处理清除连接。

如果您的连接生命周期对应于包含类的生命周期,则实现IDisposable并在Dispose中清除连接。


0

在使用“using”时不需要再加上try..finally,因为“using”本身就是一个try..finally


-4

我可以建议这个:


    class SqlOpener : IDisposable
    {
        SqlConnection _connection;

        public SqlOpener(SqlConnection connection)
        {
            _connection = connection;
            _connection.Open();

        }

        void IDisposable.Dispose()
        {
            _connection.Close();
        }
    }

    public class SomeDataClass : IDisposable
    {
        private SqlConnection _conn;

        //constructors and methods

        private void DoSomethingWithTheSqlConnection()
        {
            //some code excluded for brevity
            using (SqlCommand cmd = new SqlCommand("some sql query", _conn))
            using(new SqlOpener(_conn))
            {
                int countOfSomething = Convert.ToInt32(cmd.ExecuteScalar());
            }
            //some code excluded for brevity
        }

        public void Dispose()
        {
            _conn.Dispose();
        }
    }

希望这能有所帮助 :)

-1:无法工作!SqlCommand 仍然保持空引用。请不要修复它 - 只需坚持上面的标准解决方案。 - Joe
实际上,只要代码“周围”实际上创建了连接,我就没有任何运行问题。如果“_conn”没有正确初始化,它当然不会起作用。 - Torbjörn Gyllebring

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