Member 12561559 Ответов: 1

Внутренний.Ошибка поставщика данных NET framework 12


Привет,

Я постараюсь как можно лучше объяснить ошибку, которую я "иногда" получаю.

У меня есть VB.NET DLL, которая вызывается из приложения на ферме серверов, может быть, 100 пользователей, а также веб-сайт, сидящий на ферме, которая вызывает DLL.

Поскольку все мои пользователи из разных компаний, у всех них есть свои собственные базы данных, поэтому DLL запрашивает таблицу, чтобы вернуть строку подключения для соответствующей базы данных для этого пользователя.

Иногда существует множество вызовов из различных библиотек DLL, которые выполняют различные функции, но вызывают эту таблицу, чтобы получить ее connectionstring, а затем идут выполнять некоторую работу с этой базой данных, будь то вставка/обновление/удаление или выбор, а также циклические таблицы данных для обработки данных. Таким образом, существует множество подключений к SQL2000.

В настоящее время приложение также работает в VB6, и у нас никогда не было никаких проблем (более 10 лет!), но с тех пор, как я начал обновлять его до .NET 4.0, я начинаю получать
Цитата:
Внутренняя ошибка поставщика данных .Net Framework 12

и
иногда указывается:
Цитата:
Ссылка на объект не установлена на экземпляр объекта

что я предполагаю, вероятно, из-за неудачного соединения.

Делая различные поиски по этому вопросу, кажется, что-то связано с моим подключением DLL к базе данных SQL Server (2000) - я использую OLEDB для подключения, и результаты google, где я смотрел, похоже, указывают на потоковую обработку OLEDB, которая не очень хороша - моя DLL и другие приложения не используют потоки напрямую, но я думаю, что с ним несколько раз вызываются из приложения VB6, некоторые приложения .net win forms и несколько других приложений. vb.net это уже слишком.

Я просто не понимаю, как VB6 грамотно справляется с этим оком уже более десяти лет, а потом, когда я запускаю его vb.net он валится кучей. Когда я запускаю вещи как один пользователь или запускаю их локально на своей машине VM dev, я не получаю проблемы, это когда вещи забиваются несколькими пользователями, начинают появляться ошибки.

Любая помощь была бы очень признательна, это заставляет меня подниматься на стену, и я не знаю, где искать дальше :(

Заранее спасибо :)

Карл

УЛУЧШИТЬ ВОПРОС:

Итак, у меня есть DLL (CalculateScore), которую я вызываю из библиотеки DLL VB6, которая вызывается с классической страницы ASP. (У меня есть библиотеки DLL для фонового кодирования, так как один и тот же код используется совместно с приложением VB6 и Winforms)

Библиотека CalculateScore сначала вызывает другую библиотеку .net dll, называемую CONNMAN, которая извлекает строку подключения и возвращает ее обратно в calculatescore, чтобы знать, какую базу данных открыть на SQL Server 2000. Причина, по которой у меня есть библиотека CONNMAN dll, заключается в том, что существуют другие библиотеки DLL, которые вызывают эту библиотеку DLL для возврата соответствующей строки подключения.

Функция CALCULATESCORE DLL открыла Connman:

Public Function OpenPassConnection(ByVal CorporationID As String, ByVal TestProj As Boolean) As Boolean

       Try


           Dim NewConn As New Connman.ConnMan

           myConnectionString = NewConn.ReturnConnectionString(P3AddON_CORPID)

           NewConn = Nothing


           If myConnectionString <> "" Then
               GConn = New OleDb.OleDbConnection
               GConn.ConnectionString = myConnectionString
               GConn.Open()
           End If

       Catch ex As Exception
           Using sw As IO.StreamWriter = IO.File.AppendText(System.IO.Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location) & "\PASS4_CALCSCORE" & CorporationID & "_errorlog.txt")
               sw.WriteLine(Now & " - OpenPassConnection - " & ex.Message)
           End Using
           '  Windows.Forms.MessageBox.Show("OpenPassConnection:" & Err.Description, "Pass4", Windows.Forms.MessageBoxButtons.OK, Windows.Forms.MessageBoxIcon.Error)
           Return False
       End Try

       Return True

   End Function



Эта библиотека dll "CONNMAN" открывается следующим образом:


resultstr=
OpenPassConnection(ByVal CorporationID As String) As String


Функция просматривает текстовый файл в текущем каталоге, чтобы установить соединение с базой данных SQL Server 2000, содержащей записи по каждой компании, и возвращает строку соединения из поля, а затем открывает соединение и выполняет некоторую работу (я вставлю это после этой функции ниже):

Public Function OpenPassConnection(ByVal CorporationID As String) As String


       Dim myConnectionString As String = ""
           Dim Pass4DBConnectionStr As String = ""
           Dim SQLStr As String

       Dim path As String = System.IO.Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location) & "\Connection\ConnStr.txt"

       Try

           If FileIO.FileSystem.FileExists(path) = False Then
               Return "Unable to find file:" & path
           End If

           Dim sr As StreamReader = New StreamReader(path)
           myConnectionString = sr.ReadLine()
           sr.Close()

           If myConnectionString <> "" Then
               Gconn = New OleDb.OleDbConnection
               Gconn.ConnectionString = myConnectionString
               Gconn.Open()
           End If

           ' Now lets get the main connectionstring for the database and return it
           SQLStr = "SELECT [ServerDBConnString] FROM [Pass4_Connections] WHERE [CorpID]='" & FixsQuote(CorporationID) & "'"
           Pass4DBConnectionStr = GetAnyDetail(CorporationID, SQLStr, "ServerDBConnString", "")


       Catch ex As Exception
           Return "ERROR:" & ex.Message
       End Try

       Return Pass4DBConnectionStr

   End Function



поэтому когда строка соединения возвращается в CalculateScore и открывает это соединение и получает этот бит кода:

SQLstr = "SELECT CREATE_PERCENT,impacts,propertyref,subjective,increase_improve,mechanism,[assessmentsummary].* from [questionheading] inner join [assessmentsummary] on [questionheading].[questionheadingref]=[assessmentsummary].questionheadingref where [assessmentsummary].assessmentsummaryref=" & AssRef
           da = New OleDb.OleDbDataAdapter(SQLstr, GConn)
           ds = New DataSet
           da.Fill(ds, "GettingItem")
           If ds.Tables(0).Rows.Count = 0 Then
               da.Dispose()
               ds.Dispose()
               da = Nothing
               ds = Nothing

               Using sw As IO.StreamWriter = IO.File.AppendText(System.IO.Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location) & "\PASS4_" & CorporationID & "_errorlog.txt")
                   sw.WriteLine(Now & " - NO RECORDS ERROR")
               End Using

               Return "No Records, ERROR"
           End If


Если я вызываю этот код через приложение winforms, отлаживаю и выполняю его, я не получаю никаких ошибок. "иногда" он падает на этот код с (различными ошибками, в зависимости от того, насколько интенсивно используется):

ExecuteReader requires an open and available Connection. The connection's current state is connecting.


Internal .Net Framework Data Provider error 12.



Object reference not set to an instance of an object.



Просмотр этих ошибок (в частности, текущее состояние подключения) привел меня к тому, что вы упомянули об «Использование» и «Завершение использования» - теперь я могу заключить все в это, вместо того, чтобы открывать глобальную функцию, но я действительно использую .close .dispose и = ничего в конце моего использования (я довольно ОКР, чтобы убедиться, что я закрываю вещи после того, как он был открыт), но может ли кто-нибудь сказать мне <> с помощью "Использование" и "Завершить использование "лучше (не так ли?), чем открытие соединения в функции и закрытие в конце моего кода, что я всегда делал в VB? Я знаю, что мне нужно многому научиться при переходе с VB на .NET, но я хотел бы понять. Благодарность!

Что я уже пробовал:

Я обнаружил, что в VB6 мои библиотеки DLL являются "квартирными потоками" - в VB6 такой опции нет. VB.NET studio 2015-я думаю, мне нужно сделать мои библиотеки DLL квартирно-резьбовыми-я не слишком беспокоюсь о производительности, так как у меня не было никаких проблем с настройкой VB6, мне просто нужно устранить свою проблему

Richard MacCutchan

Ссылка на объект, не установленная на экземпляр объекта, означает, что у вас где-то есть ошибка, которая не была обнаружена в VB6. И из того, что вы написали выше, вполне возможно, что это имеет какое-то отношение к Мутли-потоку. Вполне возможно, что ваш код не является потокобезопасным. Но единственный способ узнать это-много отлаживать.

Member 12561559

Я знаю, что код работает, так как он отлично работает локально. Единственное, что я вижу, что часто встречается там, где происходят ошибки, и я не знаю, имеет ли это значение или нет, поскольку он отлично работал в VB6 и отлично работал локально, - это то, что я передаю oledb.connection через параметр функции. Я раздену их и глобально объявлю соединение oledb. Обновит этот поток, если это его разберет. Хотя Спасибо, что перезвонили мне, очень ценю.

Richard MacCutchan

- Подожди! Прежде чем что-то менять, вы должны быть уверены, что проблема именно в этом. Просто потому, что что-то работает в VB6, это не значит, что оно не содержит ошибок. Возможно, VB6 просто маскирует проблему.

Karthik_Mahalingam

Это общая ошибка, используйте отладчик, чтобы точно указать на ошибку.

Member 12561559

Я знаю, что код работает, так как он отлично работает локально. Единственное, что я вижу, что часто встречается там, где происходят ошибки, и я не знаю, имеет ли это значение или нет, поскольку он отлично работал в VB6 и отлично работал локально, - это то, что я передаю oledb.connection через параметр функции. Я раздену их и глобально объявлю соединение oledb. Обновит этот поток, если это его разберет. Хотя Спасибо, что перезвонили мне, очень ценю.

Richard Deeming

Это звучит так, как будто Вы делитесь одним OleDbConnection объект через несколько вызовов. Это не очень хорошая идея - класс не был разработан, чтобы быть потокобезопасным, и вы в конечном итоге испортите его внутреннее состояние, что приведет к странным ошибкам.

Вместо этого вы должны создать OleDbConnection когда вам это нужно, и положите его в Using блок, чтобы убедиться, что он будет утилизирован должным образом.

Эта проблема не возникла бы с VB6, потому что VB6 был в основном однопоточным.

Но сейчас мы можем только догадываться, в чем проблема, потому что вы не поделились ни одним своим кодом. Если вам нужен более конкретный совет, нажмите кнопку "улучшить вопрос" и добавьте к нему соответствующие части вашего кода.

Member 12561559

Спасибо Ричарду, я обновил вопрос - другие поиски также привели меня к оператору Using - если вы не возражаете против ответа на то, что я сделал - я, вероятно, использую старые плохие привычки из VB, и хотя он работает в .NET, он не работает "хорошо". Спасибо - я очень ценю то время, которое вы тратите на ответы.

Member 12561559

Не могли бы вы ответить еще на один вопрос - больше для моего здравомыслия, чем для чего-либо.
Я преобразовал свой код так, что у меня нет публичного oledb-соединения, но вместо этого я использую блок USING для открытия и выполнения некоторой работы - однако мне нужно вызвать другие функции в моей DLL и заставить его передать oledb-соединение, открытое в операторе USING, через эти функции - будет ли это держать его потокобезопасным или мне нужно сделать больше, используя блоки в этих отдельных функциях, чтобы открыть новое соединение и закрыть его, когда оно будет завершено?

Richard Deeming

Это должно быть прекрасно. Вам нужно только беспокоиться, если вы храните объект соединения в поле. Если вы передаете его в качестве параметра, он почти всегда будет локальным для текущего потока.

Member 12561559

Идеальный. Вы очень помогли мне, Ричард, большое спасибо.

1 Ответов

Рейтинг:
9

Richard Deeming

Как я и подозревал, вы делите одну комнату. OleDbConnection экземпляр для нескольких вызовов. Все ошибки, которые вы описываете, вызваны несколькими потоками, пытающимися получить доступ к одному объекту одновременно.

Например:
Резьба 1: Gconn = New OleDb.OleDbConnection
Резьба 1: Gconn.ConnectionString = myConnectionString
Резьба 2: Gconn = New OleDb.OleDbConnection
Резьба 1: Gconn.Open() - Ошибка: не установлена строка подключения

Или:
Резьба 1: Gconn = New OleDb.OleDbConnection
Резьба 1: Gconn.ConnectionString = myConnectionString
Резьба 1: Gconn.Open()
Резьба 2: Gconn = New OleDb.OleDbConnection
Резьба 2: Gconn.ConnectionString = myConnectionString
Резьба 1: (Пытается использовать GConn) - Ошибка: соединение не открыто

Изменяя свой код для создания соединения, когда это необходимо, а не совместно используя один глобальный объект соединения, вы избежите всех этих условий гонки.


Вы также используете конкатенацию строк для построения своих запросов, что делает ваш код уязвимым для SQL-инъекция[^]. Вместо этого вам нужно использовать правильно параметризованные запросы.


Кроме того, вы должны создавать исключение, когда что-то идет не так, а не возвращать недопустимую строку подключения. В противном случае ваш вызывающий код должен проверить все возможные возвращаемые значения "что-то пошло не так", прежде чем использовать возвращаемое значение в качестве строки подключения.


Ваша функция "connman" будет выглядеть примерно так:

Public Function OpenPassConnection(ByVal CorporationID As String) As String
    Dim path As String = System.IO.Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location) & "\Connection\ConnStr.txt"
    If Not System.IO.File.Exists(path) Then
        Throw New System.IO.FileNotFoundException(Nothing, path)
    End If
    
    Dim myConnectionString As String
    Using sr As System.IO.StreamReader = System.IO.File.OpenText(path)
        myConnectionString = sr.ReadLine()
    End Using
    
    If String.IsNullOrWhiteSpace(myConnectionString) Then
        Throw New InvalidOperationException("Pass4 connection string not found.")
    End If
    
    Using connection As New OleDb.OleDbConnection(myConnectionString)
        Using command As New OleDb.OleDbCommand("SELECT [ServerDBConnString] FROM [Pass4_Connections] WHERE [CorpID] = @CorpID", connection)
            command.Parameters.AddWithValue("@CorpID", CorporationID)
            
            connection.Open()
            Dim result As Object = command.ExecuteScalar()
            If result Is Nothing Then
                Throw New InvalidOperationException(String.Format("No connection string was found for corporation '{0}'", CorporationID))
            End If
            
            Return Convert.ToString(result)
        End Using
    End Using
End Function


В OpenPassConnection функция в "calculatescore", вы бы удалили все ссылки на GConn Вместо этого сохраните только строку подключения в поле.

Затем в любой функции, где вам нужно подключиться к базе данных, вы создадите новую OleDbConnection использование сохраненной строки подключения:
Const SQLstr As String = "SELECT CREATE_PERCENT, impacts, propertyref, subjective, increase_improve, mechanism, [assessmentsummary].* from [questionheading] inner join [assessmentsummary] on [questionheading].[questionheadingref] = [assessmentsummary].questionheadingref where [assessmentsummary].assessmentsummaryref = @AssRef"

Using connection As New OleDb.OleDbConnection(myConnectionString)
    Using da As New OleDb.OleDbDataAdapter(SQLstr, connection)
        da.SelectCommand.Parameters.AddWithValue("@AssRef", AssRef)
        
        Dim ds As New DataSet()
        da.Fill(ds, "GettingItem")
        
        If ds.Tables.Count = 0 OrElse ds.Tables(0).Rows.Count = 0 Then
            ' TODO: This should be extracted to a common logging function - preferably using a library like NLog or Serilog.
            Dim logPath As String = System.IO.Path.GetDirectoryName(Reflection.Assembly.GetExecutingAssembly().Location) & "\PASS4_" & CorporationID & "_errorlog.txt"
            Using sw As IO.StreamWriter = IO.File.AppendText(logPath)
                sw.WriteLine(Now & " - NO RECORDS ERROR")
            End Using
        End If
    End Using
End Using



Все, что вы хотели знать о SQL-инъекции (но боялись спросить) | Трой Хант[^]
Как я могу объяснить SQL-инъекцию без технического жаргона? | Обмен Стеками Информационной Безопасности[^]
Шпаргалка по параметризации запросов / OWASP[^]


Member 12561559

Единственное, что будет проблемой с тем, чтобы открывать соединение чаще, чем что - то глобальное, - это то, что я буду открывать и закрывать соединения-ужасно много. Это то, что я просто должен принять и смириться?

Richard Deeming

Это не проблема в .NET - пул соединений[^] сохраняет многоразовый пул базовых соединений открытым и повторно использует их потокобезопасным способом.

Member 12561559

Сладкий. Это здорово. Спасибо!!!