.net中的SqlConnection在使用using{}语句块时没有被关闭

16

请帮忙!

背景信息

我有一个 WPF 应用程序,它访问运行在本地机器上的 SQL Server 2005 数据库。每当我使用 Linq DataContext 时,都会使用 using { } 语句,并传入一个函数的结果,该函数返回已打开并使用 SqlCommand 执行过的 SqlConnection 对象,然后返回给 DataContext 构造函数。例如:

// In the application code
using (DataContext db = new DataContext(GetConnection()))
{
    ... Code 
}

getConnection函数的代码如下(为了方便阅读,我已经去掉了一些不必要的细节,但没有遗漏任何功能)。

// Function which gets an opened connection which is given back to the DataContext constructor
public static System.Data.SqlClient.SqlConnection GetConnection()
{
   System.Data.SqlClient.SqlConnection Conn = new System.Data.SqlClient.SqlConnection(/* The connection string */);

    if ( Conn != null )
    {
        try
        {
            Conn.Open();
        }
        catch (System.Data.SqlClient.SqlException SDSCSEx)
        {
             /* Error Handling */
        }

        using (System.Data.SqlClient.SqlCommand SetCmd = new System.Data.SqlClient.SqlCommand())
        {
            SetCmd.Connection = Conn;
            SetCmd.CommandType = System.Data.CommandType.Text;

            string CurrentUserID = System.String.Empty;
            SetCmd.CommandText = "DECLARE @B VARBINARY(36); SET @B = CAST('" + CurrentUserID + "' AS VARBINARY(36)); SET CONTEXT_INFO @B";

            try
            {
                SetCmd.ExecuteNonQuery();
            }
            catch (System.Exception)
            {
                /* Error Handling */
            }
        }

        return Conn;
    }

我认为应用程序是WPF应用程序与我遇到的问题没有任何关系。

我遇到的问题

尽管在Sql Server Management Studio中SqlConnection和DataContext被处理掉了,但我仍然可以看到有大量打开的连接:

status : 'Sleeping' 
command : 'AWAITING COMMAND' 
last SQL Transact Command Batch : DECLARE @B VARBINARY(36); SET @B = CAST('GUID' AS VARBINARY(36)); SET CONTEXT_INFO @B

最终,连接池被用尽了,应用程序无法继续运行。

因此,我只能得出这样的结论:以某种方式运行 SQLCommand 来设置 Context_Info 意味着当 DataContext 被处理掉时,连接没有被处理掉。

有人能找出明显阻止连接被关闭和处理的原因吗?

6个回答

19

根据MSDNDataContext Constructor(IDbConnection)):

如果提供了一个打开的连接,DataContext不会关闭它。因此,除非有充分的理由这样做,否则不要使用已经打开的连接来实例化DataContext。

基本上看起来你的连接在等待GC将其最终释放之前。如果你有很多代码这样做,一种方法可能是在数据上下文的部分类中重写Dispose()方法并关闭连接 - 只需确保记录下数据上下文承担连接的所有权!

    protected override void Dispose(bool disposing)
    {
        if(disposing && this.Connection != null && this.Connection.State == ConnectionState.Open)
        {
            this.Connection.Close();
            this.Connection.Dispose();
        }
        base.Dispose(disposing);
    }

就我个人而言,如果我“使用”连接(允许我执行多个操作),我非常乐意为它(常规数据上下文,不使用上述hack)提供一个开放的连接 - 即。

using(var conn = GetConnection())
{
   // snip: some stuff involving conn

   using(var ctx = new FooContext(conn))
   {
       // snip: some stuff involving ctx
   }

   // snip: some more stuff involving conn
}

7

LINQ DataContext使用的SqlProvider只有在它自己打开连接的情况下(通过SqlConnectionManager.DisposeConnection)才会关闭SQL连接。如果您将已经打开的SqlConnection对象传递给DataContext构造函数,它不会为您关闭它。因此,您应该这样写:

using (SqlConnection conn = GetConnection())
using (DataContext db = new DataContext(conn))
{
    ... Code 
}

4
我在使用Entity Framework时遇到了同样的问题。我的ObjectContext被包裹在一个using块中。
当我调用SaveChanges()时建立了连接,但是当using语句超出作用域后,我注意到SQL Management Studio仍然对.NET SQL客户端有一个"AWAITING COMMAND"。 看起来这与ADO.NET提供程序的行为有关,该提供程序默认情况下启用连接池。
来自MSDN上的"使用连接池与SQL Server"(我强调):
连接池减少了需要打开新连接的次数。池维护物理连接的所有权,通过保持每个给定连接配置的一组活动连接来管理连接。每当用户在连接上调用 Open 时,池会查看是否有可用的连接。如果有可用的池化连接,则返回它给调用方,而不是打开新连接。当应用程序在连接上调用 Close 时,池会将其返回到池化的一组活动连接中,而不是实际关闭它。一旦连接被返回到池中,它就可以在下一个 Open 调用中重复使用。

如果需要,ClearAllPoolsClearPool 也很有用,可以显式关闭所有池化连接。


1

我认为该连接已不再被引用,正在等待 GC 完全处理它。

解决方案:

创建自己的 DataContext 类,该类派生自自动生成的类(重命名基类以避免更改其他代码)。

在您的 DataContext 类中添加一个 Dispose() 函数,在其中释放内部连接。


你可以添加一个部分类来扩展自动生成的数据上下文,无需子类化。 - Marc Gravell

1

谢谢你们的帮助,问题已经解决了。

实际上,我采用了大多数答案中的元素,并按照上述方式实现了DataContext构造函数(我已经重载了构造函数,所以这不是一个很大的改变)。

// Variable for storing the connection passed to the constructor
private System.Data.SqlClient.SqlConnection _Connection;

public DataContext(System.Data.SqlClient.SqlConnection Connection) : base(Connection)
{
    // Only set the reference if the connection is Valid and Open during construction
    if (Connection != null)
    {
        if (Connection.State == System.Data.ConnectionState.Open)
        {
            _Connection = Connection;                    
        }
    }           
}

protected override void Dispose(bool disposing)
{        
    // Only try closing the connection if it was opened during construction    
    if (_Connection!= null)
    {
        _Connection.Close();
        _Connection.Dispose();
    }

    base.Dispose(disposing);
}

这样做的原因是,dispose方法中访问this.Connection会抛出一个ObjectDisposedException异常。

而上述方法正如我所希望的那样有效!


嘿,那就是我说的。我一定很聪明。 - GeekyMonkey

0

Dispose应该关闭连接,正如MSDN所指出的:

如果SqlConnection超出范围,它将不会被关闭。因此,您必须通过调用Close或Dispose来显式关闭连接。Close和Dispose在功能上是等效的。如果连接池值Pooling设置为true或yes,则底层连接将返回到连接池中。另一方面,如果Pooling设置为false或no,则与服务器的底层连接将关闭。

我猜你的问题可能与GetContext()有关。


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