【问题标题】:ADO Recordset Not Staying Open in VBA - "Operation is not allowed when the object is closed"ADO 记录集未在 VBA 中保持打开 - “对象关闭时不允许操作”
【发布时间】:2015-09-14 08:55:15
【问题描述】:

我真的不明白这里发生了什么。我正在使用 Excel VBA 连接到 SQL Server Express 数据库并返回 ADO 记录集。最初我可以让它工作,但我的代码有点乱,所以我创建了一个新模块并复制了代码,边走边整理。

现在,当我尝试运行 Sub 只是为了返回记录计数时,我收到错误“对象关闭时不允许执行该操作”。代码在 MsgBox 行中断。

这是简化的代码:

Dim Server As String
Server = "ServerName"

Dim Database As String
Database = "DatabaseName"

Dim UserID As String
UserID = "UserID"

Dim Pwd As String
Pwd = "Password"

Dim StoredProcedure As String
StoredProcedure = "StoredProcedureName"

Dim conn As New ADODB.Connection
conn.ConnectionString = "Driver={SQL Server};Server=" & Server & "; Database=" & Database & "; UID = " & UserID & "; PWD=" & Pwd & ""
conn.Open

Dim Cmd As New ADODB.Command
Cmd.ActiveConnection = conn
Cmd.CommandText = StoredProcedure
Cmd.CommandType = adCmdStoredProc

Dim params() As String
ReDim Preserve params(4, 2)

params(0, 0) = "Param1"
params(1, 0) = CStr(adInteger)
params(2, 0) = CStr(adParamInput)
params(4, 0) = CStr(6)

params(0, 1) = "Param2"
params(1, 1) = CStr(adInteger)
params(2, 1) = CStr(adParamInput)
params(4, 1) = CStr(6)

params(0, 2) = "Param3"
params(1, 2) = CStr(adInteger)
params(2, 2) = CStr(adParamInput)
params(4, 2) = CStr(15)

Dim sParam4 as String
If Not sParam4 = "" Then
    ReDim Preserve params(4, UBound(params, 2) + 1)
    params(0, UBound(params, 2)) = "Param4"
    params(1, UBound(params, 2)) = CStr(adChar)
    params(2, UBound(params, 2)) = CStr(adParamInput)
    params(3, UBound(params, 2)) = "1"
    params(4, UBound(params, 2)) = sParam4
End If

Dim sParam5 as String
If Not sParam5 = "" Then
    ReDim Preserve params(4, UBound(params, 2) + 1)
    params(0, UBound(params, 2)) = "Param5"
    params(1, UBound(params, 2)) = CStr(adChar)
    params(2, UBound(params, 2)) = CStr(adParamInput)
    params(3, UBound(params, 2)) = Len(sParam5)
    params(4, UBound(params, 2)) = sParam5
End If

Dim Prm As ADODB.Parameter
Set Prm = New ADODB.Parameter
Dim i As Integer
For i = 0 To UBound(params, 2)
    If params(1, i) = CStr(adChar) Then
        Set Prm = Cmd.CreateParameter(params(0, i), CInt(params(1, i)), CInt(params(2, i)), CInt(params(3, i)))
        Cmd.Parameters.Append Prm
        Cmd.Parameters(params(0, i)).Value = params(4, i)
    Else
        Set Prm = Cmd.CreateParameter(params(0, i), CInt(params(1, i)), CInt(params(2, i)))
        Cmd.Parameters.Append Prm
        Cmd.Parameters(params(0, i)).Value = CInt(params(4, i))
    End If
Next i

Dim rs As New ADODB.Recordset
rs.CursorLocation = adUseClient
rs.Open Cmd, , adOpenStatic, adLockOptimistic

MsgBox ("Success! " & rs.RecordCount & " Records Returned!")

当我在 Locals 窗口中查看 rs 变量时,所有属性都被列为显示相同的错误消息。对我来说,似乎记录集可以正确打开,但随后会立即自行关闭。

奇怪的是原来的(凌乱的)子现在也不起作用,抛出同样的错误。我认为我没有更改任何内容,只是将其复制到新的子目录中。

我已经注释掉了旧 sub 所在的整个模块,以防万一发生任何冲突的变量情况。这没有任何区别。

我就是看不出有什么问题!我做了很多研究和阅读,在我未经训练但充满热情的眼中,一切似乎都很好。

任何帮助将不胜感激。

编辑:这是存储过程:

USE [MyDatabase]
GO
/****** Object:  StoredProcedure [dbo].[MyProcedure]    Script Date: 14/09/2015 11:39:00 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:      <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
ALTER PROCEDURE [dbo].[MyProcedure] 
    -- Add the parameters for the stored procedure here
    @Param1 int, @Param2 int, @Param3 int, @Param4 char(1), @Param5 varchar(20) = NULL, @Param6 varchar(20) = NULL, @Param7 varchar(20) = NULL
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;
    SET ANSI_WARNINGS OFF;

    -- Insert statements for procedure here
    DECLARE @cols AS NVARCHAR(MAX), @query  AS NVARCHAR(MAX)

SET @cols = '';
SELECT @cols = @cols + ',' + QUOTENAME([Field3]) FROM View1
WHERE Field2 = @Param2 AND Field1 = @Param1

SET @cols = STUFF(@cols,1,1,'');


IF @Param5 IS NOT NULL
   SET @query = 'SELECT Field1, Field2, Field3, Field4, Field5, Field6, Field7, Field8, Field9, Field10, Field11, Field12, Field13, Field14,' + @cols + ' FROM
(
    SELECT
        Table1.Field1, 
        Table1.Field2, 
        Table1.Field3, 
        Table1.Field4, 
        Table1.Field5, 
        Table1.Field6, 
        Table1.Field7, 
        Table1.Field8, 
        Table1.Field9, 
        Table1.Field10, 
        Table1.Field11, 
        Table1.Field12, 
        Table1.Field13, 
        Table1.Field14, 
        Table2.Field2, 
        Table3.Field1
    FROM Table3 
        LEFT OUTER JOIN Table2 ON Table3.Field1 = Table2.Field1 
            AND Table3.Field2 = Table2.Field2
        LEFT OUTER JOIN Table4 ON Table3.Field4 = Table4.Field1 
            AND Table3.Field2 = Table4.Field3 
        RIGHT OUTER JOIN Table1 ON Table2.Field1 = Table1.Field1
            AND Table2.Field2 = Table1.Field15
    WHERE Table1.Field2 = ' + CAST(@Param3 AS char(1)) + '
        AND Table1.Field12 = ''' + @Param5 + '''
        AND Table1.Field15 = ' + CAST(@Param1 AS char(2)) + '
) 
AS UP
PIVOT(MAX(Field2) FOR UP.ID IN (' + @cols + ')) AS PVT '
ELSE IF @Param6 IS NOT NULL
   SET @query = 'SELECT Field1, Field2, Field3, Field4, Field5, Field6, Field7, Field8, Field9, Field10, Field11, Field12, Field13, Field14,' + @cols + ' FROM
(
    SELECT 
        Table1.Field1, 
        Table1.Field2, 
        Table1.Field3, 
        Table1.Field4, 
        Table1.Field5, 
        Table1.Field6, 
        Table1.Field7, 
        Table1.Field8, 
        Table1.Field9, 
        Table1.Field10, 
        Table1.Field11, 
        Table1.Field12, 
        Table1.Field13, 
        Table1.Field14, 
        Table2.Field2, 
        Table3.Field1
    FROM Table3 
        LEFT OUTER JOIN Table2 ON Table3.Field1 = Table2.Field1
            AND  Table3.Field2 = Table2.Field2
        LEFT OUTER JOIN Table4 ON Table3.Field4 = Table4.Field1 
            AND Table3.Field2 = Table4.Field3 
        RIGHT OUTER JOIN Table1 ON Table2.Field1 = Table1.Field1
            AND Table2.Field2 = Table1.Field15
    WHERE Table1.Field2 = ' + CAST(@Param3 AS char(1)) + '
        AND Table1.Field13 = ''' + @Param6 + '''
        AND Table1.Field15 = ' + CAST(@Param1 AS char(2)) + '
) 
AS UP
PIVOT(MAX(Field2) FOR UP.ID IN (' + @cols + ')) AS PVT '

ELSE IF @Param7 IS NOT NULL

    SET @query = 'SELECT Field1, Field2, Field3, Field4, Field5, Field6, Field7, Field8, Field9, Field10, Field11, Field12, Field13, Field14,' + @cols + ' FROM
(
    SELECT 
        Table1.Field1, 
        Table1.Field2, 
        Table1.Field3, 
        Table1.Field4, 
        Table1.Field5, 
        Table1.Field6, 
        Table1.Field7, 
        Table1.Field8, 
        Table1.Field9, 
        Table1.Field10, 
        Table1.Field11, 
        Table1.Field12, 
        Table1.Field13, 
        Table1.Field14, 
        Table2.Field2, 
        Table3.Field1
    FROM Table3 
        LEFT OUTER JOIN Table2 ON Table3.Field1 = Table2.Field1
            AND Table3.Field2 = Table2.Field2
        LEFT OUTER JOIN Table4 ON Table3.Field4 = Table4.Field1 
            AND Table3.Field2 = Table4.Field3 
        RIGHT OUTER JOIN Table1 ON Table2.Field1 = Table1.Field1
            AND Table2.Field2 = Table1.Field15
    WHERE Table1.Field2 = ' + CAST(@Param3 AS char(1)) + '
        AND Table1.Field14 = ''' + @Param7 + '''
        AND Table1.Field15 = ' + CAST(@Param1 AS char(2)) + '
) AS UP

PIVOT(MAX(Field2) FOR UP.ID IN (' + @cols + ')) AS PVT '

ELSE

    SET @query = 'SELECT Field1, Field2, Field3, Field4, Field5, Field6, Field7, Field8, Field9, Field10, Field11, Field12, Field13, Field14,' + @cols + ' FROM
(
SELECT 
    Table1.Field1, 
    Table1.Field2, 
    Table1.Field3, 
    Table1.Field4, 
    Table1.Field5, 
    Table1.Field6, 
    Table1.Field7, 
    Table1.Field8, 
    Table1.Field9, 
    Table1.Field10, 
    Table1.Field11, 
    Table1.Field12, 
    Table1.Field13, 
    Table1.Field14, 
    Table2.Field2, 
    Table3.Field1
FROM Table3 
    LEFT OUTER JOIN Table2 ON Table3.Field1 = Table2.Field1
        AND Table3.Field2 = Table2.Field2
    LEFT OUTER JOIN Table4 ON Table3.Field4 = Table4.Field1 
        AND Table3.Field2 = Table4.Field3 
    RIGHT OUTER JOIN Table1 ON Table2.Field1 = Table1.Field1
        AND Table2.Field2 = Table1.Field15
WHERE Table1.Field2 = ' + CAST(@Param3 AS char(1)) + '
    AND Table1.Field3 = ''' + @Param4 + '''
    AND Table1.Field15 = ' + CAST(@Param1 AS char(2)) + '
) AS UP

PIVOT(MAX(Field2) FOR UP.ID IN (' + @cols + ')) AS PVT '

EXECUTE (@query)

Set NOCOUNT OFF;
END

我知道参数背后的逻辑不是很……合乎逻辑……但它现在按我需要的方式工作。在我努力的过程中,我会改变它。

我还应该说,当我第一次发布问题时,我省略了处理 SP 参数的 VBA 代码部分。我现在刚下班,但我回家后会添加它。我不认为问题出在那儿。我基本上处理参数的字符串数组并将它们附加到Cmd 对象。

编辑:我现在添加了用于创建参数并将它们传递给Cmd 对象的代码。再一次,这有点令人费解,但这是我当时想到的,它确实有效。填充记录集后,我将重新审视逻辑。

【问题讨论】:

  • 我还建议您查看您的存储过程。如果它不返回记录,您将得到一个封闭的记录集。如果它先进行一些其他处理并且您没有使用SET NOCOUNT ON,也会发生这种情况
  • @RobMethven 您永远无法测试变量是否为 Nothing,因为一旦您引用它就会重新初始化。
  • @MatthewD 否 - 如果您将 Command 对象作为源传递并且还为 ActiveConnection 参数指定任何内容,则会出现错误。
  • @Rory ALTER PROCEDURE [dbo].[MyProcedure] -- Add the parameters for the stored procedure here AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; SET ANSI_WARNINGS OFF; -- Insert statements for procedure here DECLARE @cols AS NVARCHAR(MAX), @query AS NVARCHAR(MAX) --- SQL Code Here EXECUTE (@query) Set NOCOUNT OFF; END
  • @RobMethven 这再次向我表明你没有取回任何记录。您实际上在哪里选择任何东西?

标签: vba excel ado recordset


【解决方案1】:

请尝试以下代码:

Public Sub AdoTestConnection()
Dim conServer As ADODB.Connection
Dim rstResult As ADODB.Recordset
Dim strDatabase As String
Dim strServer As String
Dim strSQL As String

strServer = "YourServerName"
strDatabase = "YourDatabaseName"

Set conServer = New ADODB.Connection
conServer.ConnectionString = "PROVIDER=SQLOLEDB; " _
    & "DATA SOURCE=" & strServer & "; " _
    & "INITIAL CATALOG=" & strDatabase & "; " _
    & "User ID=" & strLogin & ";" _
    & "Password=" & strPassword
On Error GoTo SQL_ConnectionError
conServer.Open
On Error GoTo 0

Set rstResult = New ADODB.Recordset
strSQL = "set nocount on; "
strSQL = strSQL & "select  1 "
rstResult.ActiveConnection = conServer
On Error GoTo SQL_StatementError
rstResult.Open strSQL
On Error GoTo 0

If Not rstResult.EOF And Not rstResult.BOF Then
    MsgBox "Connection worked. Server returned " & rstResult.Fields(0).Value
Else
    MsgBox "Connection worked. The server did not return any value."
End If

Exit Sub

SQL_ConnectionError:
MsgBox "Problems connecting to the server." & Chr(10) & "Aborting..."
Exit Sub

SQL_StatementError:
MsgBox "Connection established. Yet, there is a problem with the SQL syntax." & Chr(10) & "Aborting..."
Exit Sub

End Sub

如果上述代码有效,那么您可以使用您的过程更改 SQL 命令,如下所示:

strSQL = "set nocount on; "
strSQL = strSQL & "exec StoredProcedureName @Parm1 = " & intValue1 & ", "
strSQL = strSQL & "                         @Parm2 = " & intValue2 & ", "
strSQL = strSQL & "                         @Parm3 = " & intValue3 & ", "
strSQL = strSQL & "                         @Parm4 = N'" & strValue1 & "', "
strSQL = strSQL & "                         @Parm5 = N'" & strValue2 & "', "
strSQL = strSQL & "                         @Parm6 = N'" & strValue3 & "', "
strSQL = strSQL & "                         @Parm7 = N'" & strValue4 & "' "

我强烈赞成这种方法而不是您当前的方法,因为它更容易调试。如果您的 SQL 语法遇到问题,您可以简单地请求 strSQL 的内容,如下所示:

Debug.Print strSQL

然后您可以将结果复制到 SQL Server Management Studio (SSMS) 并在那里验证结果。 您甚至可能得出的结论是,您不想使用存储过程并将 SP 的全部内容复制到您的 VBA 代码中。

【讨论】:

  • 这有效 - 我得到“连接有效。服务器返回 1”。但是,我使用rs.open Cmd 打开记录集的原因是我已经动态地将一堆参数对象添加到Cmd 对象中。
  • 对.. 这行得通!记录集现在按预期填充。谢谢!不完全确定现在该做什么。我很高兴将其标记为答案,因为您在我正在进行的项目中为我解决了一个紧迫的问题。但是,我也想知道为什么我的原始代码不起作用。通过阅读文档和查看大量示例,更不用说我之前有相同的代码工作了,我的原始代码应该可以工作!显然我做错了什么,但我只是想知道是什么。我应该只发布一个新问题:“为什么这不起作用?”
  • 如果您愿意,可以发布一个新问题。我当然不能帮你解决这个问题,尽管罗里在上面这么多 cmets 上付出了巨大的努力,但似乎也无法弄清楚。也许你会找到这个新问题的答案。也许不吧。也许这只是一个错误。但是,请注意,我很久以前就放弃了使用ADODB.Command 的方法!根本没有意义。上述解决方案清晰易读,可以复制到 SSMS 中进行验证。我为什么要使用ADODB.Command?
  • 已售出!有了这个评论,你几乎说服了我,甚至试图找出它为什么不起作用。我以后会用这个方法。也许我会在以后作为一个单独的问题重新发布,但现在,我非常感谢你和@Rory 在这方面花费的时间。
【解决方案2】:

评论有点长,所以我会把它放在这里作为可能的答案。请尝试:

Dim rs As ADODB.Recordset
Set rs = New ADODB.Recordset
With rs
    Set .ActiveConnection = conn
    .LockType = adLockOptimistic
    .CursorLocation = adUseServer
    .CursorType = adOpenForwardOnly
    .Open "SET NOCOUNT ON"
End With
rs.Open Cmd, , , , adCmdStoredProc

MsgBox ("Success! " & rs.RecordCount & " Records Returned!")

【讨论】:

  • 对不起,没有运气,仍然得到同样的错误信息。我正在整理 SP 代码,以便我可以将其正确发布到此处。准备好后,我会将其添加到原始问题中。
  • 如果你把上面的 cmets 的所有优点都包括在内会更好。
【解决方案3】:

我遇到了类似的问题,发现有两件事会通过 ODBC 杀死查询结果

  • 在最终输出之前处理的中间值计数。在查询开始时使用“set nocount on”修复

  • 空值被聚合掉 - 直接在 SQL Server 上运行查询会显示消息“警告:空值已被聚合或其他 SET 操作消除”。通过追踪它们中的每一个并用空字符串、零或任何有意义的低值替换并且不影响查询结果来修复。

我认为在这两种情况下,警告消息都是在记录之前输出的,因此存储记录集被该警告“填充”,但没有数据。在 SQL Server 上,这些警告静默地存在于消息日志中,但不会影响查询结果,因此人们倾向于让它们继续存在并继续进行更高优先级的工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多