为啥极少数情况下 bof/eof 之一对于新的非空记录集是正确的
Posted
技术标签:
【中文标题】为啥极少数情况下 bof/eof 之一对于新的非空记录集是正确的【英文标题】:Why extremely occasionally will one of bof/eof be true for a new non-empty recordset为什么极少数情况下 bof/eof 之一对于新的非空记录集是正确的 【发布时间】:2010-11-04 04:32:21 【问题描述】: set recordsetname = databasename.openrecordset(SQLString)
if recordsetname.bof <> true and recordsetname.eof <> true then
'do something
end if
2 个问题:
上述测试可能会错误地评估为假,但很少见 (我有一个潜伏在我的代码中,但它今天失败了,我相信这是 5 年来日常使用中的第一次——这就是我发现它的方式)。为什么对于非空记录集,bof/eof 中的一个偶尔会为真。它似乎如此罕见,以至于我想知道它为什么会发生。
这是一个万无一失的替代品吗:
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
【问题讨论】:
这只是对单个表的简单选择,在它失败的情况下/应该返回大约十几个记录。 “bof/eof 之一为真”——你的意思是异或(“使用异或逻辑对两个表达式进行逐位比较”),即一个为真,另一个为假? 我认为这只是一个普通的 OR,(即一个或两个) 我曾经和一位业务分析师一起工作,他会使用“和”这个词来编写 OR 逻辑,例如“……可以是这个,也可以是那个……” 【参考方案1】:文档明确指出,如果您打开没有记录的Recordset
:
BOF
会是真的
EOF
会是真的
RecordCount
将是 0
对于非空的 Recordset
,BOF
和 EOF
在超出第一条或最后一条记录之前都不为真。
是否有时,其他人可能会在您刚刚打开的记录集中的一个表中添加/删除记录并更改结果集? 这可能是竞争条件的结果。
您可以在Recordcount
上进行测试,而不是使用BOF
或EOF
:如果记录集为空,则始终为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
它非常具有防御性,你必须适应你的环境,但它每次都能正常工作。
关于您的第二个问题,打开记录集后进行测试(BOF
或EOF
)应该比And
版本更安全,尽管我自己会使用Recordcount
。
根据您修改后的问题进行编辑:
从您添加到问题中的代码中,我看到了几个问题,主要是您的 SQL 语句丢失和ORDER BY
子句。
问题是您希望结果集位于 Begin Order
后跟 End Order
序列中,但您的 SQL 语句并不能保证这一点。
在大多数情况下,由于您使用自动增量作为 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
这将使整个事情更加健壮。
【讨论】:
Renaud,你可能不会相信我,但 SQL 中有一个“按 ID 排序”。我这么说的原因是,对我来说,核心问题是非空记录集是否可以通过指向 bof 或 eof 的指针打开,我只是想排除可能以错误顺序返回记录的选项。我遇到的问题是,如果不是这种情况,我在我的代码中看不到任何关于为什么会发生这种情况的答案。我用谷歌搜索了这个,发现了 1 或 2 个其他旧示例,这似乎发生在其他人身上。我还运行了 800,000 个记录集的模拟,但没有发生一次 我真的只是希望能够关闭这个错误并解释它是由表达式 rs.bof true 和 rs.eof true 评估为 false 时有实际记录存在。这样我就可以睡得更香了!【参考方案2】:我一直使用的模式是:
Set rs = db.OpenRecordset(...)
Do while Not rs.EOF
' Rest of your code here.
rs.MoveNext
Loop
我从未见过这种失败(还没有!)。此处对此进行了描述:How to: Detect the Limits of a DAO Recordset
顺便说一句,Allen Browne 的 VBA 陷阱:Working with Recordsets 可能会很有趣。
【讨论】:
感谢 Mitch,我相信您的回答意味着 bof 对于非空记录集永远不会为真(因为没有 movefirst ),并且它进一步暗示 eof 永远不会为真,否则将不会进入循环所以它是和我的一样,我认为永远不会失败,但似乎失败了。 您能缩小具体情况吗?我从未见过 DAO (2.7 +) 失败 米奇,我不是逻辑天才,如果我错了,请纠正我。让 bof 是 T1 和 'NOT bof' F1 和 eof T2 和 'NOT eof' F2 所以我似乎失败的条件是 (F1 AND F2) Allen Browne 提倡 'Not (T1 AND T2)' 包含 3 种可能性(F1 AND F2)、(F1 和 T1) 和 (T1 和 F2)。所以这就像他假设 bof 或 eof 可以出现在非空记录集中。顺便说一句,我相信'NOT(T1 AND T2)'等同于(F1 OR F2)。这是正确的吗? 情况是一个共享 .mdb 文件,有 6 个并发连接,它是对单个表的简单选择,其中一个列在 where 子句中的值。真的很简单,应该返回一些记录。随后出现了明显的异常现象,所有基于记忆深刻的步骤序列的复制尝试都失败了【参考方案3】:@Renaud Bompuis 的回答非常好。让我强调一点,对于非空记录集,DAO 记录计数永远不会为零,这是我在确定记录集是否返回记录时唯一测试过的。我使用 .EOF 循环遍历记录,但在我已经测试过是否返回记录之前,不要开始单步执行记录。
【讨论】:
看起来测试记录计数是 dao 记录集的方法。只是一个问题,您认为有可能吗?或者您是否见过这样的情况:当记录集不为空时,使用指向 bof 或 eof 的指针打开了一个 dao 记录集? 我从不使用记录集测试 BOF 和 EOF,所以,不,我从未见过。我的意思是你担心错误的事情,因此运行了错误的测试。其次,当您可以从测试一个属性中获得相同的信息时,您正在测试两个属性。对我来说,这似乎是一个完全明智的选择——失去 BOF 和 EOF 作为测试空 DAO 记录集的方法。 谢谢大卫。我同意你的观点,但这是一个让我担心的特定错误的原因。我寄希望于 (rs.bof true AND rs.eof true) 的评估是“错误的”。顺便说一句,当我计划在某个阶段从 Jet 迁移到 SQL Server 时,我将不得不从 DAO 迁移到 ADO,并且我不想在我可以提供帮助的地方引入特定于 DAO 的测试。据我了解,空 ADO 记录集的记录计数值可能与等效的 DAO 不同。所以我可能会选择 NOT(rs.bof 和 rs.eof) 以使代码更广泛地适用? 如果你打算在 SQL Server 中使用 ODBC 链接表,那么你最好坚持使用 DAO。如果您不打算使用 ODBC,那么您不妨放弃整个应用程序并从头开始。 建议重写整个代码库以允许从 DAO 更改为 ADO 听起来非常极端。【参考方案4】:这是 DAO,对吧?我自己更像是一个 ADO 人,但 IIRC 在某些情况下(动态集?)您需要导航 EOF 以便评估最终的行数。在这种状态下,EOF 是否为真,BOF 是否为假(因为它尚未被导航),但一旦 BOF 被导航,它就为真(显然)并且 EOF 仍然为真。大概零行时的初始状态应该是瞬时的,但五年一次的怪异时间事件意味着您在非常早期的初始状态中捕获了它?
【讨论】:
不能指望 DAO 记录集的记录数是准确的。如果您需要准确的记录数,请执行 .MoveLast。但这确实不会将记录指针带到记录集的末尾,所以这应该与它无关。 "但是不会将记录指针移到记录集的末尾" -- 是的,要做到这一点,您必须调用鲜为人知的 .MovePastEOF 方法,该方法确实会带您越过记录集的末尾,您会发现自己身处希尔伯特大酒店,所有放大器都连接到 11。 它带你到最后一条记录,当你在最后一条记录时,你不是EOF。如果您随后发出了 MoveNext,这将使 EOF 为真。所以,我不太明白你为什么要轻视我的观点,因为这显然是真的。 套用 Spinal Tap:您在记录集上处于 EOF。你可以从那里去哪里?在哪里?无处。确切地。我们所做的是,如果我们需要额外的推力越过悬崖,你知道我们会做什么吗?我们将记录指针移到记录集的末尾。 你为什么不把 *that* 指向你的记录集的 EOF? [暂停] 这个指针越过记录集的末尾。 为了不那么晦涩:你说,“但是不会使记录指针超过记录集的末尾”这确实是真的,因为如果你花时间想一想,这是不可能的。如果在记录集末尾之外有一个可导航点,则可以很好地调用 this 为记录集的末尾。这可以无限进行,就像在希尔伯特自相矛盾的大酒店里接待客人一样。相反,记录集是有限的并且在 EOF 处结束。没有办法可以过去,没有任何地方可以到达。【参考方案5】:我偶尔会在访问中遇到完全相同的错误(今天在 Access 2007 中已链接到 sql server 后端),其中语句
如果 rst.bof 和 rst.eof
尽管 rst 代表一个空的记录集,但评估为 false。当它发生时,VBA 启动并且直接窗格中的调试器显示,确实 rst.bof 为真,rst.eof 为真,所以它似乎发生了一毫秒,然后被纠正,但在测试了逻辑之后。
【讨论】:
您阅读其他答案了吗?为空的 DAO 记录集测试 .Recordcount=0 更加可靠和简单。此外,您可能会考虑 TRUE 和 NOT FALSE 之间的区别。假设在所有布尔表示中 FALSE 为 0,测试 NOT FALSE 总是比测试 TRUE 更可靠。 丹,你的情况相似但不同。当集合不为空时,我的是“bof true 和 eof true”评估为 false。只有当我在我发布的表达式中使用布尔 AND 而不是 OR 时,这种情况才会发生在我身上。我们的情况看起来仍然相关。是我们还是Access!! DWF:这是一个非常有趣的观察结果,鉴于“if rs.bof”比“if rs.bof false”更简洁,我猜想这样做的人并不多。在我自己的情况下,我养成了写“bof true”而不是“bof = false”的习惯(没有充分的理由)。也许我需要改变它。 我的观点是,这是一个深入访问的错误,其中异步进程在正确设置 bof 和 eof 之前将控制权返回给我们的 VBA 代码。我认为某些构造(例如 if rst.bof false 和 rst.eof FALSE 有时可能会在其他构造(例如 if not rst.eof)时起作用的原因是该代码的编译方式不同并且可能会改变时间。尽管 bof/eof 测试“应该”起作用,但我的经验是,尽管很少,但它很容易失败。我用 if rst.recordcount 【参考方案6】:Here's a possible solution
可能是您的表单或模块已损坏。导出/导入受影响的模块或表单,或尝试 /decompile 选项。在我的例子中,一个不应该有的查询返回为空,但我认为核心问题可能是相似的。
【讨论】:
以上是关于为啥极少数情况下 bof/eof 之一对于新的非空记录集是正确的的主要内容,如果未能解决你的问题,请参考以下文章
返回 MIN 和 MAX 值并忽略空值 - 使用前面的非空值填充空值