2008-11-18 1 views
2

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

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, у которой будут собственные дети. Имея это в виду, я собираюсь сделать это правильно?

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


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

Другие, чем обычная ошибка:

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, у которой будут собственные дети. Имея это в виду, я собираюсь сделать это правильно?

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

+0

Вы получили сообщение об ошибке? – 2008-11-18 19:48:28

+0

Функция застревает или возвращает ошибку? Вы проверили данные для циклов? – 2008-11-18 19:57:04

ответ

2

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

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

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

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

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

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

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

0

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

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

Edit: Хорошо, я стараюсь быть более явным.

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

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

1

Выполнение SQL-соединений?

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

Возможно, вы сможете оставить подробную информацию об этой ошибке.

+0

Он не создает новое соединение каждый раз - он пытается использовать одно и то же соединение с несколькими наборами записей, которые IIRC не поддерживается в классическом asp. Но это на правильном пути. – 2008-11-18 19:57:18

+0

Я думаю, что ADO поддерживает объединение пулов, поэтому у него не заканчиваются соединения. Вот мой источник http://www.15seconds.com/issue/970531.htm – MrChrister 2008-11-18 20:07:33

0

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

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

1

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

0

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

0

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

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

0

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

0

Основываясь на sugestions, я попытаюсь переместить запрос в 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

Я работаю код с тем же сценарием.

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

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

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

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