16

请帮忙!

背景资料

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

在我使用 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 与 Sql Server Management Studio 中的 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 被释放时连接不会被释放。

当他们使用的 DataContext 被处置时,任何人都可以发现任何明显的东西会阻止连接被关闭和处置吗?

4

6 回答 6

19

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

就个人而言,只要我“使用”连接(允许我执行多个操作),我很乐意给它(常规数据上下文,没有上面的 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
}
于 2008-11-06T15:01:45.020 回答
7

SqlProviderLINQ 使用的仅DataContext关闭 SQL 连接(通过SqlConnectionManager.DisposeConnection)如果它是打开它的连接。如果你给构造函数一个已经打开的SqlConnection对象DataContext,它不会为你关闭它。因此,你应该写:

using (SqlConnection conn = GetConnection())
using (DataContext db = new DataContext(conn))
{
    ... Code 
}
于 2008-11-06T15:02:17.047 回答
3

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

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

来自MSDN上的“使用 SQL Server 连接池” (强调我的):

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

ClearAllPools如果需要,ClearPool显式关闭所有池连接似乎也很有用。

于 2009-03-31T06:45:26.200 回答
1

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

解决方案:

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

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

于 2008-11-06T15:02:41.133 回答
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);
}

这样做而不是上面的一些建议的原因是this.Connection在 dispose 方法中访问会引发ObjectDisposedException

上面的工作和我希望的一样好!

于 2008-11-06T15:34:26.390 回答
0

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

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

我的猜测是您的问题与GetContext().

于 2008-11-06T15:02:03.490 回答