代码之家  ›  专栏  ›  技术社区  ›  Jerry Dodge

无法将空字符串传递到非空数据库字段

  •  7
  • Jerry Dodge  · 技术社区  · 7 年前

    我在一件应该非常直截了当的事情上左右为难。我有一个SQL Server数据库,我正在尝试用空字符串更新不可为null的varchar或nvarchar字段。我知道这是可能的,因为一个空字符串 '' 与相同的事情 NULL . 但是,使用 TADOQuery ,它不允许我这样做。

    我正在尝试更新现有记录,如下所示:

    ADOQuery1.Edit;
    ADOQuery1['NonNullFieldName']:= '';
    //or
    ADOQuery1.FieldByName('NonNullFieldName').AsString:= '';
    ADOQuery1.Post; //<-- Exception raised while posting
    

    如果字符串中有任何内容,即使只有一个空格,也会像预期的那样保存得很好。但是,如果是空字符串,则会失败:

    无法将不可为Null的列更新为Null。

    但它不是空的。这是一个空字符串 应该 工作很好。我发誓在过去我已经多次传递空字符串。

    为什么我会遇到这个错误,我应该如何解决它?


    其他详细信息:

    • 数据库:Microsoft SQL Server 2014 Express
    • 语言:Delphi 10西雅图更新1
    • 数据库驱动程序: SQLOLEDB.1
    • 正在更新的字段: nvarchar(MAX) NOT NULL
    2 回复  |  直到 7 年前
        1
  •  13
  •   MartynA    7 年前

    我可以使用SS2014、OLEDB驱动程序和 Seattle和使用MAX作为列大小和特定数字(在我的例子中是4096)创建表时的行为差异。我想我会把这作为一种选择 答案是因为它不仅显示了如何系统地研究这种差异 但也可以识别 为什么? 出现这种差异(以及如何在将来避免这种差异)。

    请参考并执行以下编写的代码,即 UseMAX 定义 忙碌的

    在执行代码之前,立即在项目选项中启用“使用调试DCU” 显示所述异常发生在 Data.Win.ADODB 第4920行

    Recordset.Fields[TField(FModifiedFields[I]).FieldNo-1].Value := Data
    

    属于 TCustomADODataSet.InternalPost 调试评估窗口显示 Data 此时是 Null .

    接下来,请注意

    update jdtest set NonNullFieldName = ''
    

    在SSMS2014查询窗口中执行,无投诉( Command(s) completed successfully. ),因此 事实是 数据 无效的 第4920行是导致问题的原因,下一个问题是“为什么?”

    首先要注意的是,表单的标题正在显示 ftMemo

    接下来,注释掉 UseMAX公司 定义、重新编译和执行。结果:无异常 请注意,现在正在显示表单的标题 ftString .

    这就是原因:使用特定的数字作为列大小意味着 RTL检索到的表元数据导致客户端 Field 待创建 作为一个 TStringField ,可以通过字符串赋值语句设置其值。

    OTOH,指定MAX时,生成的客户端 领域 属于ftMemo类型, 这是Delphi的BLOB类型之一 如果将值设置为ftMemo字段,则数据中的代码将任由您摆布。数据库。Pas,它使用 TBlobStream . 问题是,据我所知,在经过大量实验和代码跟踪之后,TMemoField使用BlobStream的方式无法正确区分将字段内容更新为“”和将字段值设置为 无效的 (与系统变型相同)。

    简而言之,每当您尝试将TMemoField的值设置为空字符串时,实际发生的情况是字段的状态设置为Null,这就是导致q.AFAICS中出现异常的原因。这是不可避免的,因此对我来说,没有明显的解决方法。

    我还没有调查 ftMemo公司 ftString公司 是由Delphi RTL代码或它所在的MDAC(Ado)层生成的:我希望它实际上是由 RecordSet TAdoQuery使用。

    QED。请注意,这种系统化的调试方法揭示了 问题(&A);只需很少的努力和零尝试和错误 我在q上的评论中试图提出的建议。

    另一点是,这个问题完全可以在没有 求助于服务器端工具,包括SMSS探查器。不需要使用探查器来检查客户端发送到服务器的内容 因为没有理由假设服务器返回的错误 不正确。这证实了我所说的从客户端开始调查。

    此外,使用动态创建的表,使用 IfDef 通过简单观察应用程序的两次运行,ed Sql可以在一个步骤中有效地隔离问题。

    密码

    uses [...] TypInfo;
    [...]
    implementation[...]
    
    const
       //  The following consts are to create the table and insert a single row
       //
       //  The difference between them is that scSqlSetUp1 specifies
       //  the size of the NonNullFieldName to 'MAX' whereas scSqlSetUp2 specifies a size of 4096
    
       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
    
    //  Following creates the table.  The define determines the size of the NonNullFieldName
    
    {$define UseMAX}
    {$ifdef UseMAX}
      S := scSqlSetUp1;
    {$else}
      S := scSqlSetUp2;
    {$endif}
    
      ADOConnection1.Execute(S);
      try
        ADOQuery1.Open;
        try
          ADOQuery1.Edit;
    
          // Get explicit reference to the NonNullFieldName
          //  field to make working with it and investigating it easier
    
          AField := ADOQuery1.FieldByName('NonNullFieldName');
    
          //  The following, which requires the `TypInfo` unit in the `USES` list is to find out which exact type
          //  AField is.  Answer:  ftMemo, or ftString, depending on UseMAX.  
          //  Of course, we could get this info by inspection in the IDE
          //  by creating persistent fields
    
          S := GetEnumName(TypeInfo(TFieldType), Ord(AField.DataType));
          Caption := S;  // Displays `ftMemo` or `ftString`, of course
    
          AField.AsString:= '';
          ADOQuery1.Post; //<-- Exception raised while posting
        finally
          ADOQuery1.Close;
        end;
      finally
        //  Tidy up
        ADOConnection1.Execute(scSqlDropTable);
      end;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    begin
      Test1;
    end;
    
        2
  •  2
  •   Jerry Dodge    7 年前

    使用时出现问题 MAX 在数据类型中。二者都 varchar(MAX) nvarchar(MAX) 利用此行为。移除时 马克斯 并将其替换为大量,例如 5000 ,则允许空字符串。