DataAdapter удаляется до достижения конца использования

Я знаю, что всегда должен удалять экземпляры 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 вернет 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

person 41686d6564    schedule 08.07.2016    source источник
comment
Вы должны изменить заголовок, чтобы включить ByRef, потому что это суть этой проблемы.   -  person Tim Schmelter    schedule 08.07.2016


Ответы (1)


Обновление: вы правы, вы не получаете инициализированный MySqlDataAdapter из методов, если переданный ByRef экземпляр используется в Using-операторе. Эти переменные доступны только для чтения. В C# вы получаете эту значимую ошибку компилятора:

Ошибка CS1657 Не удается передать «daCompanies» в качестве аргумента ref или out, поскольку это «использующая переменная».

Это задокументировано здесь:

Ошибка компилятора CS1657

Невозможно передать «параметр» в качестве аргумента ref или out, потому что «причина» Эта ошибка возникает, когда переменная передается в качестве аргумента ref или out в контексте, в котором эта переменная доступна только для чтения. Контексты только для чтения включают переменные итерации foreach, использующие переменные и фиксированные переменные.

В VB.NET вы можете сделать это (поэтому компилятор игнорирует это, что является почти ошибкой), но после этого переменная не инициализируется. Но, как упоминалось ниже, вы все равно не должны использовать этот подход.


По другому вопросу:

Если вы посмотрите на пример на MSDN вы увидите, что Microsoft также не размещает адаптер данных. Так что это не очень нужно. Сказав это, всегда рекомендуется использовать оператор Using во всем, что реализует IDisposable.

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. .

person Tim Schmelter    schedule 08.07.2016
comment
Да, именно такое впечатление я получил от этого вопроса. Так почему же он удаляется до достижения конца оператора Using? - person 41686d6564; 08.07.2016
comment
Понимаю. Спасибо за старание, помогло :) - person 41686d6564; 08.07.2016
comment
Я только что заметил ваше замечание об использовании параметров, да, я делаю это, но в этой функции я просто загружаю всю таблицу (без WHERE clause) есть ли проблема с этим? Также я не понимаю, к чему может привести SELECT * FROM? - person 41686d6564; 08.07.2016
comment
@GeniuSBraiN: я не использую параметры, потому что в данном случае они не нужны. Но разрешая sql-строку в качестве параметра, вы также разрешаете: SELECT * FROM Tbl WHERE ID='';DELETE FROM Tbl. Так что лучше всего не создавать sql-запросы, объединяя строки или передавая их. Запрос sql всегда должен быть строковой константой. Например: SELECT Col1 FROM tbl WHERE ID=@ID - person Tim Schmelter; 08.07.2016
comment
Здесь вы найдете дополнительную информацию, почему SELECT * является плохая привычка в рабочем коде. - person Tim Schmelter; 08.07.2016