为什么对于新的非空记录集,bof/eof 之一偶尔会成立

发布于 2024-07-24 14:14:30 字数 1458 浏览 7 评论 0原文

 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 记录集名称.bof <>   true 或 recordsetname.eof <>   那么是真的 
      

编辑以添加代码详细信息:

客户有订单,每个订单以 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
 set recordsetname = databasename.openrecordset(SQLString)
    if recordsetname.bof <> true and recordsetname.eof <> true then
    'do something
    end if

2 questions :

  1. the above test can evaluate to false incorrectly but only extremely rarely
    (I've had one lurking in my code and it failed today, I believe for the first time in 5 years of daily use-that's how I found it). Why very occasionally will one of bof/eof be true for a non-empty recordset. It seems so rare that I wonder why it occurs at all.

  2. Is this a foolproof replacement:

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

Edit to add details of code :

Customers have orders, each order begins with a BeginOrder item and end with an EndOrder item and in between are the items in the order.

The SQL is:

' 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 '

In this case which looks this this

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

梦过后 2024-07-31 14:14:30

该文档明确指出,如果您打开一个没有记录的 Recordset

  • BOF 将为 true
  • EOF 将为 true
  • RecordCount< /code> 将是 0

对于非空 Recordset,在移动之前 BOFEOF 都不是 true超出第一个或最后一个记录。

是否有时其他人可能会向您刚刚打开的记录集中的表之一添加/删除一条记录并更改结果集?
这可能是竞争条件的结果。

您可以在 Recordcount 上进行测试,而不是使用 BOFEOF:如果记录集为空,则它始终为 0
如果记录集不为空,通常会在记录集打开后立即返回1; 在这种情况下,Recordcount 并不是一个昂贵的操作。
真正返回实际记录数的唯一方法是在调用Recordcount之前发出MoveLast以强制加载所有记录。

通常,如果我需要以只读方式迭代结果集:

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

它非常具有防御性,您必须适应您的情况,但它可以正常工作每次。

关于你的第二个问题,打开记录集后进行测试(BOFEOF)应该比And版本更加万无一失,尽管我会使用Recordcount 我自己。

根据修改后的问题进行编辑:

从您添加到问题中的代码中,我看到了几个问题,主要的一个是您的 SQL 语句丢失,并且 ORDER BY 条款。
问题是您期望结果集位于 Begin Order 后跟 End Order 序列,但您的 SQL 语句不能保证这一点。
在大多数情况下,由于您使用自动增量作为 ID,数据库引擎将按自然顺序返回数据,但不能保证:

  • 总是会以这种方式发生
  • 原始数据按预期顺序保存,导致 ID 的顺序“错误”。

因此,只要您对结果集的顺序有期望,就必须明确对其进行排序。

我还会将这段代码重构

' 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

这将使整个事情变得更加健壮。

The documentation clearly states that, if you open a Recordset that has no records:

  • BOF will be true
  • EOF will be true
  • RecordCount will be 0

For a non-empty Recordset, neither BOF and EOF are true until you move beyond the first or last record.

Could it be that, from time to time, someone else could have added/deleted a record to one of the tables in the recordset you're just opening and change the resultset?
It could be the result of a race condition.

Rather than use BOF or EOF, you can test on Recordcount: it's always 0 if the recordset is empty.
If the recordset is not empty, it will usually return 1 right after the recordset has been open; Recordcount isn't an expensive operation in that case.
The only way to really return the actual number of records is to issue a MoveLast before calling Recordcount to force all records to be loaded.

Usually, if I need to iterate through a resultset in read-only fashion:

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

If I don't need to iterate through records but just test if anything was returned:

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

It's pretty defensive and you have to adapt to your circumstances, but it works correctly every time.

Regarding your 2nd question, testing (BOF Or EOF) after opening the recordset should be more foolproof than the And version, although I'd use Recordcount myself.

Edit following your revised question:

From the bit of code you added to your question, I see a couple of issues, the main one being that your SQL Statement is missing and ORDER BY clause.
The problem is that you are expecting the resultset to be in the Begin Order followed by End Order sequence but your SQL Statement doesn't guarantee you that.
In most cases, since you're using an autoincrement as ID, the database engine will return the data in that natural order, but there is no guarantee that:

  • It's always going to happen that way
  • That the original data was saved in the expected sequence, resulting in IDs that are in the 'wrong' order.

So, whenever you have expectations about the sequence of the resultset, you must explicitly order it.

I would also refactor this bit of code:

' 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

Into a separate function similar to:

' 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

This would make the whole thing a bit more robust.

穿越时光隧道 2024-07-31 14:14:30

我一直使用的模式是:

Set rs = db.OpenRecordset(...)

Do while Not rs.EOF

    ' Rest of your code here.

    rs.MoveNext
Loop

我从未见过这种失败(还!)。 此处对此进行了描述:如何:检测 DAO 记录集的限制

顺便说一句,Allen Browne 的 VBA 陷阱:使用记录集可能会引起兴趣。

The pattern I have always used is:

Set rs = db.OpenRecordset(...)

Do while Not rs.EOF

    ' Rest of your code here.

    rs.MoveNext
Loop

I have never seen this fail (yet!). This is described here: How to: Detect the Limits of a DAO Recordset

As an aside, Allen Browne's VBA Traps: Working with Recordsets might be of interest.

煞人兵器 2024-07-31 14:14:30

@Renaud Bompuis 的回答非常好。 让我强调一点,对于非空记录集,DAO Recordcount 永远不会为零,这是我在确定记录集是否已返回记录时测试的唯一内容。 我使用 .EOF 循环遍历记录,但在测试是否有记录返回之前不要开始逐步遍历记录。

@Renaud Bompuis's answer is quite good. Let me emphasize the point that the DAO Recordcount is never zero for a non-empty recordset, and that is the only thing I ever test in determining if a recordset has returned records. I use .EOF for looping through the records, but don't start stepping through the records until I've already tested if there are records returned.

月亮邮递员 2024-07-31 14:14:30

这就是DAO,对吧? 我自己更喜欢 ADO,但 IIRC 在某些情况下(动态集?),您需要导航 EOF 才能评估最终的行数。 是否在这种状态下 EOF 为 true,BOF 为 false(因为尚未导航),但是一旦 BOF 被导航,它就是 true(显然)并且 EOF 仍然为 true。 据推测,预计零行时的初始状态应该是瞬时的,但是五年一次的反常计时事件意味着您在非常早期的初始状态中捕获了它?

This is DAO, right? I'm more an ADO man myself but IIRC there are circumstances (dynaset?) where you need to navigate EOF in order for the final number of rows to be assessed. Could it be in this state that EOF is true, BOF is false (because it hasn't been navigated yet) but as soon as BOF is navigated it is true (obviously) and EOF remains true. Presumably the initial state when zero rows are expected is supposed to be instantaneous but a once-in-five-years freak timing incident means you captured it in a really early initial state?

夏末 2024-07-31 14:14:30

我偶尔会在 Access 中遇到完全相同的错误(今天在 Access 2007 中链接到 SQL Server 后端),其中语句

if rst.bof 和 rst.eof 的

计算结果为 false,尽管 rst 代表空记录集。 当它发生时,VBA 启动,并且立即窗格中的调试器显示,确实 rst.bof 为真,rst.eof 为真,因此它似乎发生了一毫秒,然后被纠正,但在测试了逻辑之后。

I occasionally come across the exact same bug in access (had it today in Access 2007 linked to a sql server back end) where the statement

if rst.bof and rst.eof

evaluates to false despite rst representing an empty recordset. When it happened, VBA started and the debugger in the immediate pane showed that, indeed rst.bof was true and rst.eof was true, so it seems to happen for a millisecond and then is corrected, but after one has tested the logic.

妖妓 2024-07-31 14:14:30

这是一个可能的解决方案

可能是这样您的表单或模块已损坏。 导出/导入受影响的模块或表单,或尝试 /decompile 选项。 在我的例子中,一个查询在不应该返回的时候返回了空,但我认为核心问题可能是类似的。

Here's a possible solution

It could be that your form or module has gotten corrupted. Export/Import the affected module or form, or try the /decompile option. In my case a query was coming back empty when it shouldn't have, but I think the core problem could be similar.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文