Что не так с этой рекурсивной функцией ASP? - PullRequest
2 голосов
/ 18 ноября 2008

Когда я вызываю эту функцию, все работает, пока я не пытаюсь рекурсивно вызвать функцию снова. Другими словами, если я раскомментирую строку:

GetChilds rsData("AcctID"), intLevel + 1 

Тогда функция прерывается.

<%
    Function GetChilds(ParentID, intLevel)
        Set rsData= Server.CreateObject("ADODB.Recordset")
        sSQL = "SELECT AcctID, ParentID FROM Accounts WHERE ParentID='" & ParentID &"'"
        rsData.Open sSQL, conDB, adOpenKeyset, adLockOptimistic
        If IsRSEmpty(rsData) Then
            Response.Write("Empty")
        Else
            Do Until rsData.EOF
                Response.Write rsData("AcctID") & "<br />"
                'GetChilds rsData("AcctID"), intLevel + 1 
                rsData.MoveNext
            Loop
        End If
        rsData.close: set rsData = nothing
    End Function

    Call GetChilds(1,0)
%>

* Отредактировано после обратной связи

Спасибо всем,

Кроме обычной ошибки:

Error Type: (0x80020009) Exception occurred.

Я не был уверен, что вызвало проблемы. Я понимаю, что это, вероятно, связано с несколькими факторами.

  1. Не закрывать соединение и не пытаться повторно открыть то же соединение.
  2. Для многих одновременных подключений к базе данных.

Содержимое базы данных выглядит следующим образом:

AcctID | ParentID
1        Null
2        1
3        1
4        2
5        2
6        3
7        4

Идея состоит в том, чтобы я мог иметь основную учетную запись с дочерними учетными записями, а эти дочерние учетные записи могли иметь собственные дочерние учетные записи. В конце концов появится еще одна основная учетная запись с идентификатором ParentID, равным Null, у которого будут собственные дочерние элементы. Имея это в виду, я иду по этому правильному пути?

Спасибо за быстрый ответ.


Спасибо всем,

Кроме обычной ошибки:

Тип ошибки: (0x80020009) Исключение произошло.

Я не был уверен, что вызвало проблемы. Я понимаю, что это, вероятно, связано с несколькими факторами.

  1. Не закрывать соединение и не пытаться повторно открыть то же соединение.
  2. Для многих одновременных подключений к базе данных.

Содержимое базы данных выглядит следующим образом:

AcctID | ParentID
1        Null
2        1
3        1
4        2
5        2
6        3
7        4

Идея состоит в том, чтобы я мог иметь основную учетную запись с дочерними учетными записями, а эти дочерние учетные записи могли иметь собственные дочерние учетные записи. В конце концов появится еще одна основная учетная запись с идентификатором ParentID, равным Null, у которого будут собственные дочерние элементы. Имея это в виду, я иду по этому правильному пути?

Спасибо за быстрый ответ.

Ответы [ 10 ]

2 голосов
/ 18 ноября 2008

Похоже, что это не удается, потому что ваше соединение все еще занято, обслуживая RecordSet из предыдущего вызова.

Один из вариантов - использовать новое соединение для каждого звонка. Опасность заключается в том, что у вас быстро кончатся соединения, если вы будете повторять слишком много раз.

Другой вариант - прочитать содержимое каждого RecordSet в отдельную коллекцию: (словарь, массив и т. Д.), Чтобы вы могли сразу же закрыть соединение. Затем выполните итерацию по отключенной коллекции.

Если вы используете SQL Server 2005 или более позднюю версию, есть еще лучший вариант. Вы можете использовать CTE (общее табличное выражение) для написания рекурсивного SQL-запроса. Затем вы можете переместить все в базу данных, и вам нужно выполнить только один запрос.

Некоторые другие заметки:
Поля идентификаторов обычно int s, поэтому их не следует заключать в символы 'в строке sql.

Наконец, этот код, вероятно, в порядке, потому что я сомневаюсь, что пользователю разрешено напрямую вводить идентификационный номер. Однако используемый метод динамического sql очень опасен, и его, как правило, следует избегать. Вместо этого используйте параметры запроса, чтобы предотвратить внедрение SQL.

Я не слишком беспокоюсь о том, чтобы не использовать intLevel для чего-либо. Глядя на код, очевидно, это ранняя версия, и intLevel можно использовать позже, чтобы определить что-то вроде отступа или имени класса, используемого при стилизации элемента.

1 голос
/ 18 ноября 2008

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

1 голос
/ 18 ноября 2008

Закончились соединения SQL?

Вы имеете дело с таким количеством слоев (Response.Write для клиента, ASP для сервера и базы данных), поэтому неудивительно, что возникают проблемы.

Возможно, вы можете опубликовать некоторые подробности об ошибке?

0 голосов
/ 27 февраля 2013

У меня есть рабочий код с тем же сценарием.

Я использую курсор на стороне клиента

...
rsData.CursorLocation = adUseClient
rsData.Open sSQL, conDB, adOpenKeyset, adLockOptimistic
rsData.ActiveConnectcion = Nothing
...

Как указывалось в других ответах, это не очень эффективно, я использую его только в интерфейсе администратора, где код вызывается нечасто и скорость не так критична.

Я бы не использовал такой рекурсивный процесс на обычной веб-странице. Либо переработайте код, чтобы получить все данные за один вызов из базы данных, либо выполните вызов один раз и сохраните его в локальном массиве и сохраните массив в переменной приложения.

0 голосов
/ 19 ноября 2008

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

Function GetChilds(ParentID, intLevel)
        'Open my Database Connection and Query the current Parent ID
        Set rsData= Server.CreateObject("ADODB.Recordset")
        sSQL = "SELECT AcctID, ParentID FROM Accounts WHERE ParentID='" & ParentID &"'"
        rsData.Open sSQL, conDB, adOpenKeyset, adLockOptimistic
        'If the Record Set is not empty continue
        If Not IsRSEmpty(rsData) Then
            Dim myAccts()
            ReDim myAccts(rsData.RecordCount)
            Dim i
            i = 0
            Do Until rsData.EOF
                Response.Write "Account ID: " & rsData("AcctID") & " ParentID: " & rsData("ParentID") & "<br />"
                'Add the Childs of the current Parent ID to an array.
                myAccts(i) = rsData("AcctID")
                i = i + 1
                rsData.MoveNext
            Loop
            'Close the SQL connection and get it ready for reopen. (I know not the best way but hey I am just learning this stuff)
            rsData.close: set rsData = nothing
            'For each Child found in the previous query, now lets get their childs.
            For i = 0 To UBound(myAccts)
                Call GetChilds(myAccts(i), intLevel + 1)
            Next
        End If
    End Function

    Call GetChilds(1,0)
0 голосов
/ 18 ноября 2008

Если вам нужна такая рекурсия, я бы лично поместил рекурсию в хранимую процедуру и выполнял эту обработку на стороне базы данных, чтобы избежать открытия нескольких соединений. Если вы используете mssql2005, посмотрите на что-то, называемое Common Table Expressions (CTE), они упрощают рекурсию. Есть и другие способы реализации рекурсии с другими СУБД.

0 голосов
/ 18 ноября 2008

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

Если подумать, это может быть потому, что у вас слишком много одновременных соединений БД. Вы постоянно открываете, но не собираетесь закрываться, пока не выйдете из рекурсивного цикла.

0 голосов
/ 18 ноября 2008

При каждом вызове вы открываете новое соединение с базой данных и не закрываете его перед открытием нового.

0 голосов
/ 18 ноября 2008

Как это ломается?

Я предполагаю, что после определенного количества рекурсий вы, вероятно, получаете переполнение стека (иронично), потому что вы не выделяете слишком много RecordSets.

0 голосов
/ 18 ноября 2008

попробуйте объявить переменные как локальные, используя оператор DIM в определении функции:

Function GetChilds(ParentID, intLevel)
Dim rsData, sSQL
Set ...

Редактировать: Хорошо, я пытаюсь быть более явным.

Насколько я понимаю, поскольку rsData не объявляется DIM, это не локальная переменная, а глобальная переменная. Следовательно, если вы перебираете оператор WHILE, вы достигаете .Eof самого внутреннего набора записей rsData. Вы возвращаетесь из рекурсивного вызова функции, и следующим шагом снова является rsData.MoveNext, который завершается ошибкой.

Пожалуйста, исправьте меня, если rsData действительно локальный.

...