Почему крайне редко один из bof / eof будет верен для нового непустого набора записей - PullRequest
2 голосов
/ 19 июня 2009
 set recordsetname = databasename.openrecordset(SQLString)
    if recordsetname.bof <> true and recordsetname.eof <> true then
    'do something
    end if

2 вопроса:

  1. Приведенный выше тест может дать неверное значение, но только крайне редко (У меня был один скрытый код в моем коде, и сегодня он потерпел неудачу, я полагаю, впервые за 5 лет ежедневного использования - вот как я его нашел). Почему очень редко один из bof / eof будет верным для непустого набора записей. Это кажется настолько редким, что мне интересно, почему это вообще происходит.

  2. Это надежная замена:

    if recordsetname.bof <> true or recordsetname.eof <> true then
    

Изменить, чтобы добавить детали кода:

У клиентов есть заказы, каждый заказ начинается с элемента BeginOrder и заканчивается элементом EndOrder, а между ними - элементы в заказе.

SQL:

' ids are autoincrement long integers '
SQLString = "select * from Orders where type = OrderBegin or type = OrderEnd"           

Dim OrderOpen as Boolean
OrderOpen = False

Set rs = db.Openrecordset(SQLString)
If rs.bof <> True And rs.eof <> True Then
    myrec.movelast
    If rs.fields("type").value = BeginOrder Then
         OrderOpen = True
    End If
End If

If OrderOpen F False Then
    'code here to add new BeginOrder Item to Orders table '
End If

ShowOrderHistory 'displays the customer's Order history '

В этом случае, который выглядит этот

BeginOrder
Item a
Item b
...
Item n
EndOrder

BeginOrder
Item a
Item b
...
Item n
EndOrder

BeginOrder
Item a
item b
...
Item m

BeginOrder     <----should not be there as previous order still open

Ответы [ 6 ]

3 голосов
/ 19 июня 2009

В документации четко указано, что если вы откроете Recordset, у которого нет записей:

  • BOF будет верно
  • EOF будет правдой
  • RecordCount будет 0

Для непустых Recordset ни BOF, ни EOF не верны, пока вы не выйдете за пределы первой или последней записи.

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

Вместо использования BOF или EOF вы можете проверить на Recordcount: всегда 0, если набор записей пуст.
Если набор записей не пустой, он обычно возвращает 1 сразу после того, как набор записей был открыт; Recordcount в этом случае не дорогая операция.
Единственный способ действительно вернуть фактическое количество записей - это выдать MoveLast перед вызовом Recordcount, чтобы принудительно загрузить все записи.

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

Dim db as DAO.Database
Dim rs as DAO.RecordSet

Set db = CurrentDB()
Set rs = db.OpenRecordSet("...", dbOpenForwardOnly)
If Not (rs Is Nothing) Then
    With rs
       Do While Not .EOF
            ' Do stuff '
            .MoveNext
       Loop
       .Close
    End With
    Set rs = Nothing
End If
Set db = Nothing

Если мне не нужно перебирать записи, а просто проверять, было ли что-либо возвращено:

Set rs = db.OpenRecordSet("...", dbOpenForwardOnly)
If Not (rs Is Nothing) Then
    With rs
       If .RecordCount > 0 Then
          ' We have a result '
       Else
          ' Empty resultset '
       End If
       .Close
    End With
    Set rs = Nothing
End If
Set db = Nothing

Это довольно оборонительно, и вы должны приспосабливаться к вашим обстоятельствам, но каждый раз работает правильно.

Что касается вашего второго вопроса, тестирование (BOF или EOF) после открытия набора записей должно быть более надежным, чем версия And, хотя я бы сам использовал Recordcount.

Отредактируйте после вашего исправленного вопроса:

Из фрагмента кода, который вы добавили в свой вопрос, я вижу пару проблем, основной из которых является отсутствие вашего оператора SQL и предложение ORDER BY.
Проблема в том, что вы ожидаете, что результирующий набор будет в последовательности Begin Order, за которой следует последовательность End Order, но ваш оператор SQL не гарантирует вам этого.
В большинстве случаев, поскольку вы используете автоинкремент в качестве идентификатора, ядро ​​базы данных будет возвращать данные в таком естественном порядке, но нет гарантии, что:

  • Так будет всегда
  • Исходные данные были сохранены в ожидаемой последовательности, в результате чего идентификаторы были в «неправильном» порядке.

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

Я бы также переработал этот бит кода:

' ids are autoincrement long integers '
SQLString = "select * from Orders where type = OrderBegin or type = OrderEnd"           

Dim OrderOpen as Boolean
OrderOpen = False

Set rs = db.Openrecordset(SQLString)
If rs.bof <> True And rs.eof <> True Then
   myrec.movelast
    If rs.fields("type").value = BeginOrder Then
        OrderOpen = True
    End If
End If

В отдельную функцию, похожую на:

' Returns true if the given CustID has a Open Order, '
' false if they are all closed.'
Public Function IsOrderOpen(CustID as Long) As Boolean
    Dim result as Boolean
    result = False

    Dim sql as String
    ' Here I assume that the Orders table has a OrderDateTime field that '
    ' allows us to sort the order in the proper chronological sequence '
    ' To avoid loading the complete recordset, we sort the results in a way '
    ' that will return the last used order type as the first record.'
    sql = sql & "SELECT Type " 
    sql = sql & "FROM Orders "
    sql = sql & "WHERE ((type = OrderBegin) OR (type = OrderEnd)) "
    sql = sql & "      AND (CustID=" & CustID & ")"
    sql = sql & "ORDER BY OrderDateTime DESC, Type DESC;"

    Dim db as DAO.Database
    Dim rs as DAO.Recordset
    Set db = CurrentDB()
    Set rs = db.Openrecordset(sql, dbOpenForwardOnly)

    If Not (rs Is Nothing) Then
        If rs.RecordCount > 0 Then
            result = (rs!type = BeginOrder)
        End If
        rs.Close
    End If

    Set rs = Nothing
    Set db = Nothing

    IsOrderOpen = result
End Function

Это сделало бы все это более надежным.

1 голос
/ 12 мая 2010

Я иногда сталкиваюсь с той же самой ошибкой в ​​доступе (была ли она сегодня в Access 2007 связана с серверной частью sql), где оператор

если rst.bof и rst.eof

оценивается как ложное, несмотря на то, что rst представляет пустой набор записей. Когда это произошло, VBA запустилась, и отладчик на ближайшей панели показал, что действительно, rst.bof был верным, а rst.eof - истиной, поэтому это происходит в течение миллисекунды, а затем исправляется, но после проверки логики.

1 голос
/ 21 июня 2009

@ Ответ Рено Бомпюи довольно хороший. Позвольте мне подчеркнуть, что количество записей DAO никогда не бывает нулевым для непустого набора записей, и это единственное, что я когда-либо проверял при определении, вернул ли набор записей записи. Я использую .EOF для циклического просмотра записей, но не начинаю пошагово просматривать записи, пока не проверил, возвращаются ли записи.

1 голос
/ 19 июня 2009

Шаблон, который я всегда использовал:

Set rs = db.OpenRecordset(...)

Do while Not rs.EOF

    ' Rest of your code here.

    rs.MoveNext
Loop

Я никогда не видел этот провал (пока!). Это описано здесь: Как: определить пределы набора записей DAO

Кроме того, VBA Traps Аллена Брауна: Работа с наборами записей может представлять интерес.

0 голосов
/ 20 августа 2009

Вот возможное решение

Возможно, ваша форма или модуль повреждены. Экспортируйте / импортируйте соответствующий модуль или форму или попробуйте параметр / decompile. В моем случае запрос возвращался пустым, когда его не должно было быть, но я думаю, что основная проблема может быть похожей.

0 голосов
/ 19 июня 2009

Это DAO, верно? Я больше человек ADO, но у IIRC есть обстоятельства (dynaset?), Где вам нужно перемещаться по EOF, чтобы оценить окончательное количество строк. Может ли быть в этом состоянии, что EOF является истинным, BOF является ложным (потому что он еще не был перемещен), но как только BOF перемещается, это верно (очевидно), и EOF остается истинным. Предположительно, начальное состояние, когда ожидаются нулевые строки, предполагается мгновенным, но инцидент с периодичностью один раз в пять лет означает, что вы захватили его в действительно раннем начальном состоянии?

...