DataAdapter 在到达 "End Using" 之前被释放

DataAdapter is disposed before reaching "End Using"

我知道我应该始终处理 DataAdapter 实例。在大多数情况下,我会在关闭连接后立即处理它,但在用户将修改 DataTable 项目(显示在 ListBox 或 DataGridView 中)的情况下,我会创建 DataAdapter,用它来填充 DataTable,但不要处理它直到用户点击 Save 调用 DataAdapter.Update(DataTable)... 这不是我的主要问题,但这是正确的方法吗?

回到正题,我有这两个功能:

Public Function LoadCompaniesDT(ByRef dtCompanies As DataTable) As Boolean
    Using daCompanies As MySqlDataAdapter = Nothing
        Return LoadCompaniesDT(daCompanies, dtCompanies)
    End Using
End Function

Public Function LoadCompaniesDT(ByRef daCompanies As MySqlDataAdapter, ByRef dtCompanies As DataTable) As Boolean
    Dim sql As String = "SELECT * FROM companies"
    Return LoadDT(daCompanies, dtCompanies, sql, Res.CompaniesFailedMsgBody)
End Function

它们习惯于调用 LoadDT 来填充 DataTable,因此我可以选择是否传递 DataAdapter。

现在我对一些事情感到困惑:当使用第一个 LoadCompaniesDT 函数时,daCompanies 在到达 End Using 之前被处理掉。像这样:

Public Function LoadCompaniesDT(ByRef dtCompanies As DataTable) As Boolean
    Using daCompanies As MySqlDataAdapter = Nothing
        Dim tmp As Boolean = LoadCompaniesDT(daCompanies, dtCompanies)
        Console.WriteLine(daCompanies Is Nothing) ' ==> True!!
        Return tmp
    End Using
End Function

注意:如果我使用 Dim daCompanies 而不是 Using daCompanies,那么 daCompanies Is Nothing 将 return False。


LoadDT功能代码:

Private Function LoadDT(ByRef da As MySqlDataAdapter, ByRef dt As DataTable,
                                                      ByVal sqlQuery As String,
                                                      ByVal errorText As String) As Boolean
    Dim connStr As String = String.Format("server={0}; port={1}; user id={2}; password={3}; database={4}",
                                          DbServer, DbServerPort, DbUserName, DbPassword, DatabaseName)
    Dim conn As MySqlConnection = New MySqlConnection(connStr)
    Dim cmd As MySqlCommand = New MySqlCommand

    Try
        conn.Open()

        cmd.CommandType = CommandType.Text
        cmd.CommandText = sqlQuery
        cmd.Connection = conn

        da = New MySqlDataAdapter(cmd)
        dt = New DataTable
        da.Fill(dt)

        Return True
    Catch ex As Exception
        MessageBox.Show(errorText, Res.ServerError, MessageBoxButtons.OK, MessageBoxIcon.Error)
        Return False
    Finally
        cmd.Dispose()
        cmd = Nothing
        conn.Close()
        conn.Dispose()
    End Try
End Function

Update:你是对的,如果 ByRef 传递的实例用于Using 语句。这些变量是只读的。在 C# 中,您会收到 this 有意义的编译器错误:

Error CS1657 Cannot pass 'daCompanies' as a ref or out argument because it is a 'using variable'

已记录 here:

Compiler Error CS1657

Cannot pass 'parameter' as a ref or out argument because 'reason'' This error occurs when a variable is passed as a ref or out argument in a context in which that variable is readonly. Readonly contexts include foreach iteration variables, using variables, and fixed variables.

在VB.NET中你可以这样做(所以编译器会忽略它,这几乎是一个错误)但是之后变量没有被初始化。但是如下所述,无论如何你都不应该使用这种方法。


根据另一个问题:

如果您查看 MSDN 上的示例,您会发现 Microsoft 也没有处理数据适配器。所以它真的没有必要。话虽如此,对实现 IDisposable.

的任何内容使用 Using 语句始终是最佳实践

A DataAdapter 不是昂贵的对象,它不包含非托管资源(如连接)。因此,在任何需要的地方从中创建一个新实例并没有什么坏处。你不需要处理它,但这是一个实现细节,将来可能会发生变化,或者在 DbDataAdapter 的不同实现中发生变化,所以处理它仍然是最佳做法,最好使用 Using -声明。

我不会使用您的方法,因为您将 sql 字符串传递给通常会导致 sql 注入漏洞的方法。而是使用 sql 参数。

例如:

Private Function LoadDT() As DataTable
    Dim tbl As New DataTable()
    'Load connection string from app.config or web.config
    Dim sql As String = "SELECT * FROM companies" ' don't use * but list all columns explicitely
    Using conn As New MySqlConnection(My.Settings.MySqlConnection)
        Using da = New MySqlDataAdapter(sql, conn)
            da.Fill(tbl)
        End Using
    End Using
    Return tbl
End Function

我不会传递 errorText ByRef,而是使用像 log4net.

这样的日志记录框架