【问题标题】:Wrapper that properly disposes and closes database objects正确处理和关闭数据库对象的包装器
【发布时间】:2018-09-25 16:20:11
【问题描述】:

我有一个简单的辅助函数,它在 SQL Server 数据库中查找数据并返回一个数据集。

这个函数在我的 web 应用程序的很多不同的地方都用到了。

在大多数情况下,它工作得很好。但是当有很多同时连接的并发用户时,我偶尔会得到这样的错误:

DBUtilities.getDataSet:建立与 SQL Server 的连接时发生与网络相关或特定于实例的错误。服务器未找到或无法访问。验证实例名称是否正确,并且 SQL Server 已配置为允许远程连接。

发生这种情况时,我在数据库服务器事件日志中看不到任何错误,只有 Web 服务器事件日志。

在阅读了有关垃圾收集和对象处置的内容后,我开始认为可能是数据库连接没有被关闭或正确处置。

我想知道,是否有办法将此代码放入某种类型的包装器中,以帮助正确处理数据库对象?

这也是我所指的功能:

Public Overloads Function getDataSet(ByVal commandText As String, Optional ByVal tableName As String = "") As DataSet

    Dim ds As New DataSet
    Dim conn As New SqlConnection(myConnStr)

    Try
        Dim cmd As New SqlCommand(commandText, conn)
        cmd.CommandTimeout = 30
        cmd.CommandType = CommandType.Text
        Dim da As New SqlDataAdapter
        da.SelectCommand = cmd
        conn.Open()

        If String.IsNullOrEmpty(tableName) Then
            da.Fill(ds)
        Else
            da.Fill(ds, tableName)
        End If
    Catch ex As Exception
        Throw New Exception("AppDataMethods.getDataSet: " & ex.Message & ", cmdText = " & commandText)
    Finally
        conn.Close()
        conn = Nothing
    End Try
    Return ds

End Function

【问题讨论】:

    标签: asp.net vb.net sqldataadapter


    【解决方案1】:

    Close() 调用位于 Finally 块中,所以你没事。现代代码往往更喜欢 Using 块,而 conn = Nothing 行在 VB.Net 中根本没有帮助(在 VB6/VBScript 中需要它,但不再需要了),但你所拥有的应该没问题。如果您使用 DataApater 的 Fill() 方法,也不需要调用 conn.Open()

    对我来说最可怕的事情是要求commandText 字符串,无法接受单独的参数数据。这实际上迫使您在其他代码中构建严重不安全的 SQL 字符串,或者仍然鼓励将其作为处理 SQL 的默认方式,即使 Overloads 声明在其他地方考虑了这一点。

    我自己的数据方法往往总是需要传递一个SqlParameter 数组,即使对于那些没有参数的查询(使用空数组),以确保没有其他程序员可以使用我的数据库至少对查询参数有一定的了解。或者,我有时会使用函数式方法,而是使用委托/lambda 参数来分配参数。

    我也倾向于将此方法构建为 Private,在一个专门用于数据库访问的新模块中,然后为您现有的每个查询创建公共方法。如果您有很多查询,这也可以是单独的类库项目中的 Friend 方法,因此您有多个类或模块来将查询组织到逻辑组中。

    Private Overloads Function getDataSet(ByVal commandText As String, parameters As SqlParameter(), Optional ByVal tableName As String = "") As DataSet
        Dim result As New DataSet
        Using conn As New  SqlConnection(myConnStr), _
              cmd As New SqlCommand(commandText, conn), _
              da As New SqlDataAdapter(cmd)
    
            cmd.CommandTimeout = 30
            If parameters IsNot Nothing AndAlso parameters.Length > 0 Then
                cmd.Parameters.AddRange(parameters)
            End If
    
            If String.IsNullOrWhitespace(tableName) Then tableName = "Table1"
            da.Fill(result, tableName)
    
        End Using
        Return result    
    End Function
    

    我还删除了 Catch 块,因为它所做的唯一另一件事是重新抛出与您已有的类似的异常。您会丢失 CommandText,但如果您对每个查询都有专门的方法并使用参数化查询(如其他地方所推荐的那样),那么您不会直接从堆栈跟踪中获得的 CommandText 中很少有很多信息。

    【讨论】:

    • OP 的代码没有调用 conn.Dispose() - 这可能是问题所在吗?
    • 谢谢,这段代码看起来干净多了。你认为这个新代码会提高性能吗?
    • @AndrewMorton Doubtful,因为它确实调用了 .Close()。但可能会有一些交互,但 Close() 应该是你所需要的。
    • @SkyeBoniwell 如果这意味着您正在使用参数,否则您没有使用,那么是的,因为参数化查询将帮助您在数据库服务器上保存查询编译步骤。在出现异常的情况下也是可以的,因为我们保存了一个堆栈展开并重新抛出。否则,它基本上是表达相同事物的更简单的方法。
    • @SkyeBoniwell 如果您遇到 SQL Server 性能问题,请查看How to Disable Auto Close in SQL Server,确保数据库中有有用的索引,以及您将要使用的 SQL 参数,请确保为字符串类型参数(nvarchar 等)指定数据库列大小。
    猜你喜欢
    • 2011-06-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-04
    • 2019-11-13
    相关资源
    最近更新 更多