为什么我的存储过程接收到空参数?
好的,这是卷曲的。我正在处理一些我未编写的Delphi代码,并且遇到了一个非常奇怪的问题。我的存储过程的参数之一以" null"的形式出现,即使它肯定被发送为" 1"也是如此。
Delphi代码使用TADOQuery执行存储过程(匿名):
ADOQuery1.SQL.Text := "exec MyStoredProcedure :Foo,:Bar,:Baz,:Qux,:Smang,:Jimmy"; ADOQuery1.Parameters.ParamByName("Foo").Value := Integer(someFunction()); // other parameters all set similarly ADOQuery1.ExecSQL;
Integer(SomeFunction())当前总是返回1我已通过调试器检查过的值。
但是,在我存储的proc中(出于调试目的而进行了更改):
create procedure MyStoredProcedure ( @Foo int, @Bar int, @Baz int, @Qux int, @Smang int, @Jimmy varchar(20) ) as begin -- temp debug if ( @Foo is null ) begin insert into TempLog values ( "oh crap" ) end -- do the rest of the stuff here.. end
实际上,TempLog
的结尾确实是"哦废话"(附带的问题:必须有一种更好的调试存储过程的方法:这是什么?)。
这是来自事件探查器的示例跟踪:
exec [MYDB]..sp_procedure_params_rowset N'MyStoredProcedure',1,NULL,NULL declare @p3 int set @p3=NULL exec sp_executesql N'exec MyStoredProcedure @P1,@P2,@P3,@P4,@P5,@P6', N'@P1 int OUTPUT,@P2 int,@P3 int,@P4 int,@P5 int,@P6 int', @p3 output,1,1,1,0,200 select @p3
这对我来说有点奇怪。请注意,它使用的是@ p3和@ P3,这可能导致我的问题吗?
另一个奇怪的事情是,它似乎取决于我使用哪个TADOConnection。
该项目是一个dll,该dll是从另一个应用程序传递给TADOConnection的。它使用此连接调用所有存储过程。
如果不是使用此连接,则首先执行以下操作:
ConnectionNew := TADOQuery.Create(ConnectionOld.Owner); ConnectionNew.ConnectionString := ConnectionOld.ConnectionString; TADOQuery1.Connection := ConnectionNew;
然后不会发生此问题!从这种情况的跟踪是这样的:
exec [MYDB]..sp_procedure_params_rowset N'MyStoredProcedure',1,NULL,NULL declare @p1 int set @p1=64 exec sp_prepare @p1 output, N'@P1 int,@P2 int,@P3 int,@P4 int,@P5 int,@P6 varchar(20)', N'exec MyStoredProcedure @P1,@P2,@P3,@P4,@P5,@P6', 1 select @p1 SET FMTONLY ON exec sp_execute 64,0,0,0,0,0,' ' SET FMTONLY OFF exec sp_unprepare 64 SET NO_BROWSETABLE OFF exec sp_executesql N'exec MyStoredProcedure @P1,@P2,@P3,@P4,@P5,@P6', N'@P1 int,@P2 int,@P3 int,@P4 int,@P5 int,@P6 varchar(20)', 1,1,1,3,0,'400.00'
不幸的是,对于我来说,这有点多。哪种TADOConnection选项可能会影响这一点?
有人有什么想法吗?
编辑:更新以下(不想再提这个问题了:P)
解决方案
警告:我不知道德尔福,但这个问题敲响了微弱的钟声,所以我对此感兴趣
如果使用TADOStoredProc而不是TADOQuery会得到相同的结果吗?请参见delphi 5开发人员指南
同样,看起来第一条迹线没有执行prepare调用,并认为@ P1是execute中的输出参数,而第二条迹线进行了以@ P1作为输出的prepare调用,但未在执行中显示@ P1作为输出这一步很重要吗?看起来确实很奇怪,所以可能是一个线索
我们也可以尝试将函数调用替换为常量1
祝我们好运,请告诉我们我们所发现的内容!
在我的程序中,我有很多代码与第一个代码段非常相似,并且我还没有遇到此问题。
那实际上是代码,还是我们如何表示问题以便我们理解?是将SQL文本存储在DFM中还是动态填充?
我想知道查询的Params属性是否已经在IDE中获得了已定义/缓存的参数列表,这可能解释了为什么P1被视为输出(几乎肯定会导致NULL问题)。
在设置ParamByName.Value之前,请尝试
ParamByName("Foo").ParamType=ptInput;
我不确定为什么更改连接字符串也可以解决此问题,除非它重置了该查询的参数内部含义。
在TSQLQuery下,每当更改SQL.Text值时,查询的Params属性都会被重置/重新创建(我不确定这对于TADOQuery是否正确),因此第一个代码段应该引起了任何现有的Params信息已被删除。
如果上面的" ParamByname.ParamType"建议确实为我们解决了问题,那么肯定在其他地方(在创建时?在窗体上?)上发生了查询查询,导致它认为Foo是输出参数...
这些帮助有用? :-)
我怀疑我们在先前使用ADOQuery时遗留了一些参数不匹配的情况。
我们是否尝试过在更改SQL.Text之后重设参数:
ADOQuery1.Parameters.Refresh;
我们也可以尝试清除参数并显式重新创建它们:
ADOQuery1.Parameters.Clear; ADOQuery1.Parameters.CreateParameter('Foo', ftInteger, pdInput, 0, 1); [...]
我认为更改连接实际上会强制使用参数的InternalRefresh。
ADOQuery1.Parameters.ParamByName("Foo").Value = Integer(someFunction());
他们不在对象Pascal中使用:=
进行分配吗?
@康斯坦丁
它必须是问题作者的错字。
B
嗯...当我们更改TADOQuery的SQL时,很好用
清除参数并重新创建,然后使用CreateParameter。
我不会在运行时依赖ParamCheck,因为它离开了
参数的属性大多未定义。
依靠ParamCheck来解决此类问题
自动填充参数很少,但是会发生。
嗯,如果我们走CreateParameter路线,请先创建
参数@RETURN_VALUE一个,因为它将捕获返回的
MSSQL SP的值。
我唯一遇到过这样的问题是,数据库提供程序无法区分Output(始终将其设置为null)和InputOutput(使用我们提供的参数)参数时。
好的,进步了。
@Robsoft是正确的,将参数方向设置为pdInput
可以解决此问题。
我追踪到了VCL代码,它归结为TParameters.InternalRefresh.RefreshFromOleDB
。设置SQL.Text时将调用此函数。这是(节略的)代码:
function TParameters.InternalRefresh: Boolean; procedure RefreshFromOleDB; // .. if OLEDBParameters.GetParameterInfo(ParamCount, PDBPARAMINFO(ParamInfo), @NamesBuffer) = S_OK then for I := 0 to ParamCount - 1 do with ParamInfo[I] do begin // .. Direction := dwFlags and $F; // here's where the wrong value comes from // .. end; // .. end; // .. end;
因此,由于某种原因,OLEDBParameters.GetParameterInfo
返回错误的标志。
我已经验证了原始连接(dwFlags和$ F)
是2
(DBPARAMFLAGS_ISOUTPUT
),而新连接是1
(DBPARAMFLAGS_ISINPUT
)。
至少到目前为止,我不确定我是否想对此进行更深入的研究。
在获得更多时间和兴趣之前,我将确保在打开查询之前将所有参数都设置为" pdInput"。
除非有人现在有更多聪明的主意..?
无论如何,感谢大家到目前为止的建议。