我可以使用以下代码复现你报告的问题,使用SS2014、OLEDB驱动程序和Seattle时,当表格使用MAX作为列大小和具体数字(在我的情况下为4096)创建时,行为上存在差异。我想提供这个作为一个替代答案,因为它不仅展示了如何系统地调查这个差异,而且还指出了
为什么 会出现这种差异(因此,以后应该如何避免)。请参考并执行以下代码,就像它被写出来的那样,即使激活了
UseMAX
定义。在执行代码之前,在项目选项中打开“使用Debug DCUs”,立即显示所描述的异常发生在
Data.Win.ADODB
的第4920行。
Recordset.Fields[TField(FModifiedFields[I]).FieldNo-1].Value := Data
我使用TCustomADODataSet.InternalPost
函数,调试窗口显示此时Data
为Null
。
接下来,请注意:
update jdtest set NonNullFieldName = ''
在SSMS2014查询窗口中执行没有问题(命令已成功完成
),因此似乎在第4920行Data
为Null
是导致问题的原因,下一个问题是“为什么?”
那么,首先要注意的是,窗体标题显示的是ftMemo
接下来,注释掉UseMAX
定义,重新编译并执行。结果:没有异常,并且注意到窗体标题现在显示的是ftString
。
这就是原因:使用特定的列大小数字意味着RTL检索到的表元数据会创建客户端Field
作为TStringField
,其值可以通过字符串赋值语句设置。
然而,当您指定MAX时,生成的客户端Field
的类型为ftMemo,这是Delphi的BLOB类型之一,当您将字符串值分配给ftMemo字段时,您就要看Data.DB.Pas中的代码,它使用TBlobStream
对记录缓冲区执行所有读取(和写入)。问题在于,据我所见,在经过大量实验和代码跟踪之后,TMemoField使用BlobStream的方法未能正确区分将字段内容更新为''和将字段的值设置为Null
(如System.Variants中所示)。
简言之,每当您尝试将TMemoField的值设置为空字符串时,实际发生的是该字段的状态被设置为Null,这就是在该问题中引起异常的原因。在我看来,这是不可避免的,因此对我来说,没有明显的解决方法。
我还没有调查过在Delphi RTL代码或其基于MDAC(Ado)的层上选择ftMemo
和ftString
之间的选择:我希望它实际上是由TAdoQuery使用的RecordSet
决定的。
证毕。注意,这种系统化的调试方法仅需很少的工作量和零试错,就能揭示问题及其原因,这正是我在评论中试图建议q的做法。
另一点是,完全可以跟踪此问题而无需使用服务器端工具,包括SMSS分析器。没有必要使用分析器检查客户端发送到服务器的内容,因为没有理由认为服务器返回的错误是不正确的。这证实了我关于从客户端开始调查的说法。
此外,使用用IfDef
ed Sql创建的临时表,通过观察应用程序的两次运行,有效地使问题隔离在单个步骤中。
代码
uses [...] TypInfo;
[...]
implementation[...]
const
scSqlSetUp1 =
'CREATE TABLE [dbo].[JDTest]('#13#10
+ ' [ID] [int] NOT NULL primary key,'#13#10
+ ' [NonNullFieldName] VarChar(MAX) NOT NULL'#13#10
+ ') ON [PRIMARY]'#13#10
+ ';'#13#10
+ 'Insert JDTest (ID, [NonNullFieldName]) values (1, ''a'')'#13#10
+ ';'#13#10
+ 'SET ANSI_PADDING OFF'#13#10
+ ';';
scSqlSetUp2 =
'CREATE TABLE [dbo].[JDTest]('#13#10
+ ' [ID] [int] NOT NULL primary key,'#13#10
+ ' [NonNullFieldName] VarChar(4096) NOT NULL'#13#10
+ ') ON [PRIMARY]'#13#10
+ ';'#13#10
+ 'Insert JDTest (ID, [NonNullFieldName]) values (1, ''a'')'#13#10
+ ';'#13#10
+ 'SET ANSI_PADDING OFF'#13#10
+ ';';
scSqlDropTable = 'drop table [dbo].[jdtest]';
procedure TForm1.Test1;
var
AField : TField;
S : String;
begin
{$define UseMAX}
{$ifdef UseMAX}
S := scSqlSetUp1;
{$else}
S := scSqlSetUp2;
{$endif}
ADOConnection1.Execute(S);
try
ADOQuery1.Open;
try
ADOQuery1.Edit;
AField := ADOQuery1.FieldByName('NonNullFieldName');
S := GetEnumName(TypeInfo(TFieldType), Ord(AField.DataType));
Caption := S;
AField.AsString:= '';
ADOQuery1.Post;
finally
ADOQuery1.Close;
end;
finally
ADOConnection1.Execute(scSqlDropTable);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Test1;
end;
Null
,尝试将底层记录集字段设置为#0等。尝试使用以bmWrite模式创建的AdoBlobStream执行时没有投诉,但实际上并没有更新服务器字段。令人恼火的是,执行set NonNullFieldName = ''
可以正常工作,并且其DataLength随后报告为0,因此SSMS显然知道如何做到这一点。 - MartynAftMemo
无法通过TADODataSet更新为空字符串-正如你所说,当更新时,空字符串会转换为NULL
。我没有太多时间测试代码中实际的问题所在。 - kobikRecordset.Fields[TField(FModifiedFields[I]).FieldNo-1].Value := ''
没有起作用吗?还是您没有尝试过这个方法? - kobikBufferToVar
->Data := Variant(Buffer^);
中。 - kobik