You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Classic ASP中SQL Server INSERT OUTPUT INTO返回结果异常问题

问题分析与解决:Classic ASP注册新用户时Recordset找不到responseMessage字段

问题原因

你遇到的问题核心在于ADODB.Recordset默认只会读取TSQL执行产生的第一个结果集,而你的代码在插入新用户时,TSQL里的CREATE TABLE #newUserTable语句会生成一个空的结果集(这是SQL Server对DDL语句的默认行为),后续的SELECT @responseMessage其实是第二个结果集。此时你的Recordset指向的是第一个空结果集,自然找不到responseMessage字段。

而当用户已存在时,代码不会执行CREATE TABLEINSERT逻辑,只有最后的SELECT @responseMessage作为唯一结果集,所以Recordset能正常读取到字段。这也解释了为什么直接在SSMS里执行TSQL能正常返回结果——SSMS会显示所有生成的结果集,你看到的是最后一个有效的结果。

修正方法

有两种可靠的解决方式,推荐第一种(更简洁且避免额外结果集):

方法1:用表变量代替临时表,消除额外结果集

把临时表#newUserTable替换为表变量@newUserTable,因为表变量的DECLARE语句不会产生额外的结果集,这样整个TSQL执行后只会返回最后一个SELECT的结果集。

修改后的TSQL代码如下:

tsql="DECLARE @newUserTable TABLE(UserID int); " & vbCrLf & _
"DECLARE @responseMessage NVARCHAR(50); " & vbCrLf & _
"DECLARE @userID INT; " & vbCrLf & _
"IF EXISTS (SELECT TOP 1 UserID FROM [dbo].[UserLogins] WHERE LoginName='" & username & "') " & vbCrLf & _
"BEGIN " & vbCrLf & _
"SET @responseMessage='User already exists' " & vbCrLf & _
"END " & vbCrLf & _
"ELSE " & vbCrLf & _
"BEGIN " & vbCrLf & _
"DECLARE @salt UNIQUEIDENTIFIER=NEWID(); " & vbCrLf & _
"BEGIN TRY " & vbCrLf & _
"INSERT INTO dbo.UserLogins (LoginName,PasswordHash,Salt) " & vbCrLf & _
"OUTPUT INSERTED.UserID INTO @newUserTable " & vbCrLf & _
"VALUES ('" & username & "',HASHBYTES('SHA2_512', '" & password & "'+CAST(@salt AS NVARCHAR(36))),@salt); " & vbCrLf & _
"SET @responseMessage=" & vbCrLf & _
"CAST((SELECT userID FROM @newUserTable) AS NVARCHAR(50)); " & vbCrLf & _
"END TRY " & vbCrLf & _
"BEGIN CATCH " & vbCrLf & _
"SET @responseMessage=ERROR_MESSAGE() " & vbCrLf & _
"END CATCH " & vbCrLf & _
"END " & vbCrLf & _
"SELECT @responseMessage AS N'responseMessage'; "

这里去掉了最后的DROP TABLE,因为表变量会在批处理结束后自动销毁,不需要手动删除。

方法2:在ASP代码中遍历所有结果集

如果你不想修改TSQL,可以在ASP代码中调用rs.NextRecordset()方法,跳过前面的空结果集,直到找到包含responseMessage字段的结果集:

rs.open tsql, con
' 跳过所有空的结果集,直到找到有responseMessage的或者到最后
Do While Not rs Is Nothing
    ' 检查当前结果集是否有目标字段
    On Error Resume Next ' 临时禁用错误捕获
    Dim fieldExists
    fieldExists = False
    For Each f In rs.Fields
        If f.Name = "responseMessage" Then
            fieldExists = True
            Exit For
        End If
    Next
    On Error GoTo 0 ' 恢复错误捕获
    
    If fieldExists Then
        Exit Do
    Else
        Set rs = rs.NextRecordset()
    End If
Loop

If Not rs Is Nothing And Not rs.EOF Then
    response.write rs("responseMessage")
End If
rs.close

不过这种方法不如第一种简洁,而且需要额外的错误处理,所以更推荐方法1。

额外提醒

虽然你提到熟悉Classic ASP且能满足需求,但还是要注意:你的代码存在SQL注入风险——直接把usernamepassword拼接到TSQL中是非常危险的。建议改用参数化查询,通过ADODB.Command对象传递参数,避免注入攻击。比如:

Set cmd = Server.CreateObject("ADODB.Command")
cmd.ActiveConnection = con
cmd.CommandText = "DECLARE @newUserTable TABLE(UserID int); DECLARE @responseMessage NVARCHAR(50); DECLARE @userID INT; IF EXISTS (SELECT TOP 1 UserID FROM [dbo].[UserLogins] WHERE LoginName=@username) BEGIN SET @responseMessage='User already exists' END ELSE BEGIN DECLARE @salt UNIQUEIDENTIFIER=NEWID(); BEGIN TRY INSERT INTO dbo.UserLogins (LoginName,PasswordHash,Salt) OUTPUT INSERTED.UserID INTO @newUserTable VALUES (@username,HASHBYTES('SHA2_512', @password+CAST(@salt AS NVARCHAR(36))),@salt); SET @responseMessage=CAST((SELECT userID FROM @newUserTable) AS NVARCHAR(50)); END TRY BEGIN CATCH SET @responseMessage=ERROR_MESSAGE() END CATCH END SELECT @responseMessage AS N'responseMessage';"
cmd.Parameters.Append cmd.CreateParameter("@username", 200, 1, 50, username) ' adVarChar=200, adParamInput=1
cmd.Parameters.Append cmd.CreateParameter("@password", 200, 1, 255, password)
Set rs = cmd.Execute
If Not rs.EOF Then
    response.write rs("responseMessage")
End If
rs.close

内容的提问来源于stack exchange,提问作者vurtual

火山引擎 最新活动