Почему очень редко один из bof/eof будет верным для нового непустого набора записей

 set recordsetname = databasename.openrecordset(SQLString)
    if recordsetname.bof <> true and recordsetname.eof <> true then
    'do something
    end if

2 вопроса:

  1. вышеприведенный тест может дать ошибочное значение false, но очень редко (у меня был один, скрывающийся в моем коде, и сегодня он провалился, я думаю, впервые за 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

person jjb    schedule 19.06.2009    source источник
comment
Это просто простой выбор в одной таблице, который в случае сбоя сделал/должен был вернуть около дюжины записей.   -  person jjb    schedule 19.06.2009
comment
одно из bof/eof должно быть истинным - вы имеете в виду xor (побитовое сравнение двух выражений с использованием логики исключающее или), т.е. одно истинно, а другое ложно?   -  person onedaywhen    schedule 19.06.2009
comment
я думаю, что это просто обычное ИЛИ (то есть одно или оба)   -  person jjb    schedule 19.06.2009
comment
Раньше я работал с бизнес-аналитиком, который писал логику ИЛИ, используя слово «и», например. ...может быть то, а может быть то...   -  person onedaywhen    schedule 22.06.2009


Ответы (6)


В документации четко указано, что если вы откроете 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

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

person Renaud Bompuis    schedule 19.06.2009
comment
Рено, ты наверное мне не поверишь, но в SQL есть сортировка по ID. Причина, по которой я говорю это, заключается в том, что для меня основная проблема заключается в том, может ли непустой набор записей открываться с указателем, указывающим либо на bof, либо на eof, и я просто хочу исключить вариант, что, возможно, записи были возвращены в неправильном порядке. У меня проблема в том, что если это не так, я не вижу в своем коде ответа на вопрос, почему это произошло. Я погуглил это и наткнулся на 1 или 2 других старых примера, где это, кажется, произошло с другими. Я также провел симуляцию 800 000 наборов записей, и это не случалось ни разу. - person jjb; 21.06.2009
comment
Я действительно просто надеялся закрыть эту ошибку объяснением того, что она была вызвана выражением rs.bof ‹› true и rs.eof ‹› true, оценивающим значение false, когда записи действительно присутствовали. Тогда мне будет легче спать! - person jjb; 21.06.2009

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

Set rs = db.OpenRecordset(...)

Do while Not rs.EOF

    ' Rest of your code here.

    rs.MoveNext
Loop

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

Кроме того, могут быть интересны ловушки VBA Аллена Брауна: Работа с наборами записей.

person Mitch Wheat    schedule 19.06.2009
comment
спасибо, Митч, я полагаю, что ваш ответ подразумевает, что bof никогда не будет истинным для непустого набора записей (поскольку нет movefirst ), и это также подразумевает, что eof никогда не будет истинным, иначе цикл не будет введен, так что это то же самое, что и мой, о котором я думал никогда не потерпит неудачу, но которая, кажется, имеет. - person jjb; 19.06.2009
comment
Можете ли вы сузить точные обстоятельства? Я никогда не видел такого сбоя с DAO (2.7+) - person Mitch Wheat; 19.06.2009
comment
Митч, я не гений логики, так что поправьте меня, если я ошибаюсь. Пусть bof будет T1 и 'NOT bof' F1 и eof T2 и 'NOT eof' F2 Таким образом, мое условие, которое оказалось несостоятельным, это (F1 AND F2). Аллен Браун выступает за 'Not (T1 AND T2)', которое охватывает 3 возможности (F1 AND F2), (F1 И T1) и (T1 И F2). Таким образом, он предполагает, что либо bof, либо eof могут встречаться в непустом наборе записей. Кстати, я считаю, что «НЕ (T1 AND T2)» эквивалентно (F1 ИЛИ F2). Это правильно? - person jjb; 19.06.2009
comment
обстоятельства были общим файлом .mdb с 6 одновременными подключениями, это был простой выбор в одной таблице со значением для одного столбца в предложении where. Действительно просто и должно было вернуть несколько записей. Последовала яркая и сразу очевидная аномалия, и все попытки воспроизвести на основе хорошо запомненной последовательности шагов потерпели неудачу. - person jjb; 19.06.2009

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

person David-W-Fenton    schedule 20.06.2009
comment
Похоже, тестирование количества записей — это то, что нужно для наборов записей dao. Только один вопрос, как вы думаете, это возможно, или вы когда-нибудь видели случай, когда набор записей dao был открыт с указателем, указывающим на bof или eof, когда набор записей не пуст? - person jjb; 21.06.2009
comment
Я НИКОГДА не тестирую BOF и EOF с наборами записей, так что нет, я никогда этого не видел. Я хочу сказать, что вы беспокоитесь НЕ О НЕПРАВИЛЬНОМ ВЕЩЕСТВЕ и, таким образом, проводите неправильный тест. Во-вторых, вы тестируете два свойства, когда можете получить ту же информацию при тестировании одного. Мне это кажется совершенно несложным - потерять BOF и EOF как методы тестирования для пустого набора записей DAO. - person David-W-Fenton; 22.06.2009
comment
Спасибо, Дэвид. Я понимаю вашу точку зрения, но это было причиной конкретной ошибки, которая меня беспокоила. Я возлагал надежды на то, что оценка (rs.bof ‹› true AND rs.eof ‹› true) будет «неправильной». Кстати, поскольку я планирую перейти с Jet на SQL-сервер, на каком-то этапе мне придется перейти с DAO на ADO, и я не хочу вводить тесты, специфичные для DAO, где я могу помочь. Насколько я понимаю, значение recordcount для пустого набора записей ADO может отличаться от эквивалента DAO. Так что я, вероятно, выберу НЕ (rs.bof и rs.eof), чтобы сделать код более широко применимым? - person jjb; 23.06.2009
comment
Если вы собираетесь использовать связанные таблицы ODBC с SQL Server, вам лучше придерживаться DAO. Если вы не собираетесь использовать ODBC, вы можете отказаться от всего приложения и начать с нуля. - person David-W-Fenton; 24.06.2009
comment
Предположение о том, что для перехода с DAO на ADO потребуется переписать всю кодовую базу, звучит очень экстремально. - person jjb; 26.06.2009
comment
Это то, что вы должны сделать, чтобы получить выгоду от переключения. ADO с SQL Server весьма полезен, но только с несвязанным приложением. Если вы используете связанные формы, у вас должны быть связанные таблицы (или привязка к наборам записей ADO, что может привести к всевозможным странностям). Access сияет связанными формами, и поэтому лучший способ использовать его — со связанными таблицами. Это означает, что Jet/ODBC, а это означает, что DAO — лучший интерфейс данных. Если вы враждебно относитесь к связанным формам, то я говорю, что вы враждебно относитесь к базовому дизайну Access и должны использовать что-то совершенно другое. - person David-W-Fenton; 27.06.2009
comment
DWF прошел почти год, и я все еще использую DAO и bof/eof, а не количество записей, но я все еще планирую двигаться на каком-то этапе :) В любом случае, повторное чтение этого только что вызвало эту случайную мысль. Почему вы говорите, что количество записей более надежно, чем использование bof/eof? Как вы говорите, вы проходите через записи только после того, как установили, что количество записей > 0. Возможно, если ваш код хорошо написан для защиты, вы никогда не заметите, когда это произойдет. Что Дональд Рамсфилд назвал бы неизвестным неизвестным? Кстати, я не видел эту ошибку с тех пор и очень редко - person jjb; 13.05.2010
comment
С философской точки зрения один тест лучше, чем предположительно эквивалентная пара тестов. Кроме того, из существования этого вопроса совершенно ясно, что существуют пустые наборы записей, которые не проверяют True как для EOF, так и для BOF одновременно. Мне кажется, это не проблема. - person David-W-Fenton; 14.05.2010

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

если rst.bof и rst.eof

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

person Dan    schedule 11.05.2010
comment
Вы читали другие ответы? Проверка .Recordcount=0 для пустого набора записей DAO надежнее и проще. Кроме того, вы можете рассмотреть разницу между ИСТИНА и НЕ ЛОЖЬ. Учитывая, что FALSE равно 0 во всех логических представлениях, проверка на NOT FALSE всегда будет работать более надежно, чем проверка на TRUE. - person David-W-Fenton; 13.05.2010
comment
Дэн, у вас похожая ситуация, но другая. В моем случае bof ‹› true и eof ‹› true оценивались как false, когда набор не был пустым. Это случалось со мной только тогда, когда я использовал логическое И, а не ИЛИ в выражении, которое я опубликовал. Тем не менее наши ситуации выглядят связанными. Это мы или доступ!! ДВФ: Это очень интересное наблюдение, и учитывая, что если rs.bof более лаконичен, чем если rs.bof ‹› false, я полагаю, что немногие этим занимаются. В моем собственном случае у меня появилась привычка (без веской причины) писать bof ‹› true, а не bof = false. Может быть, мне нужно изменить это. - person jjb; 13.05.2010
comment
Я считаю, что это глубокая ошибка в доступе, когда асинхронный процесс возвращает управление нашему коду VBA до того, как он правильно установил bof и eof. Я думаю, причина того, что некоторые конструкции, такие как if rst.bof ‹› false и rst.eof ‹› FALSE, могут иногда работать, когда другие, например if not rst.eof, заключается в том, что этот код скомпилирован по-другому и может изменить синхронизацию. Хотя тест bof/eof должен работать, мой опыт показывает, что он, хотя и редко, подвержен сбоям. Я заменил все свои пустые тесты набора записей на if rst.recordcount‹=0 (согласно Аллену Брауну), и это работает для DAO. - person Dan; 13.06.2010

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

person onedaywhen    schedule 19.06.2009
comment
Количество записей набора записей DAO нельзя считать точным. Если вам нужно точное количество записей, вы делаете .MoveLast. Но это не переводит указатель записи за конец набора записей, так что это не должно иметь к этому никакого отношения. - person David-W-Fenton; 21.06.2009
comment
Но это не перемещает указатель записи за конец набора записей — ах да, для этого вы должны вызвать малоизвестный метод .MovePastEOF, который действительно перенесет вас за конец набора записей, где вы оказываетесь в гранд-отеле Гильберта, где все усилители идут на 11. - person onedaywhen; 22.06.2009
comment
Это приводит вас к последней записи, и когда вы находитесь на последней записи, вы не EOF. Если бы вы затем выпустили MoveNext, это сделало бы EOF истинным. Итак, я не совсем понимаю, почему вы легкомысленно относитесь к моей точке зрения, ведь она совершенно верна. - person David-W-Fenton; 22.06.2009
comment
Перефразируя Spinal Tap: вы находитесь в EOF на своем наборе записей. Куда вы можете пойти оттуда? Где? Нигде. Точно. Что мы делаем, если нам нужен дополнительный толчок с обрыва, вы знаете, что мы делаем? Мы переносим указатель записи за конец набора записей. Почему бы вам просто не сделать *эту* точку EOF вашего набора записей? [пауза] Этот указатель проходит за конец набора записей. - person onedaywhen; 23.06.2009
comment
Чтобы быть менее неясным: вы сказали, Но это не переводит указатель записи за конец набора записей. Это действительно верно, потому что, если вы потратите время на размышления об этом, это невозможно. Если бы за концом набора записей была точка навигации, то можно было бы хорошо назвать this концом набора записей. Это могло бы продолжаться бесконечно, как размещение гостей в парадоксальном Гранд-отеле Гильберта. Вместо этого наборы записей конечны и заканчиваются на EOF; нет способов пройти мимо, нет точки за пределами того, чего вы можете достичь. - person onedaywhen; 23.06.2009

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

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

person DGM    schedule 20.08.2009