.net SqlConnection即使在using{}中也未关闭

本文关键字:SqlConnection using net | 更新日期: 2023-09-27 17:48:49

请帮忙!

背景信息

我有一个WPF应用程序,它访问SQLServer2005数据库。数据库在应用程序运行的机器上本地运行。

无论我在哪里使用Linq DataContext,我都会使用using{}语句,并传递一个函数的结果,该函数返回一个SqlConnection对象,该对象已打开并在返回DataContext构造函数之前使用它执行了SqlCommand。。即

// 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应用程序与我遇到的问题有任何关系

我遇到的问题

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

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被释放时,连接不会被释放。

当使用连接的DataContext被处理时,有人能发现任何明显的东西来阻止连接被关闭和处理吗?

.net SqlConnection即使在using{}中也未关闭

来自MSDN(DataContext 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);
    }

就我个人而言,我很乐意给它(常规数据上下文,没有上面的破解)一个开放的连接,只要我"使用"连接(允许我执行多个操作)-即

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
}

LINQ DataContext使用的SqlProvider仅在SQL连接(通过SqlConnectionManager.DisposeConnection)是打开它的对象时才关闭它。如果您将已经打开的SqlConnection对象提供给DataContext构造函数,它将不会为您关闭它。因此,你应该写:

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

我在使用实体框架时遇到了同样的问题。我的ObjectContext被包裹在一个using块上。

当我调用SaveChanges()时建立了连接,但在using语句超出范围后,我注意到SQL Management Studio仍然有一个用于.NET SQL客户端的"AWAITING COMMAND"。这似乎与ADO.NET提供程序的行为有关,该提供程序默认情况下启用了连接池。

来自MSDN上的"使用SQL Server连接池"(重点是我的):

连接池减少了需要打开新连接的次数。池程序维护物理连接的所有权。它通过为每个给定的连接配置保持一组活动连接来管理连接。每当用户在连接上调用Open时,池程序都会查看池中是否有可用的连接。如果池连接可用,它会将其返回给调用者,而不是打开新连接当应用程序在连接上调用Close时,池程序会将其返回到活动连接的池集中,而不是实际关闭它。一旦连接返回到池中,就可以在下一次Open调用中重用它

此外,如果需要,ClearAllPoolsClearPool似乎可以显式关闭所有池连接。

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

解决方案:

创建自己的DataContext类,该类派生自自动生成的类。(重命名基本代码,这样您就不必更改任何其他代码)。

在派生的DataContext中,添加一个Dispose()函数。在那里-处理内部连接。

感谢各位的帮助,现在已经解决了。。

从本质上讲,我采用了上面大多数答案的元素,并实现了上面的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

上面的效果和我所希望的一样好!

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

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

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