5

有这样的记录并不少见:

TAddress = record
  Address: string[50];
  City   : string[20];
  State  : string[2];
  ZIP    : string[5];
end;

最好有硬编码的字符串大小,以确保字符串的大小不会超过为数据分配的数据库字段大小。

然而,鉴于该ShortString类型已被弃用,Delphi 开发人员正在做什么来“解决”这个问题?将记录字段声明为string完成工作,但不能保护数据不超过适当的长度。

这里最好的解决方案是什么?

4

6 回答 6

5

如果我必须防止数据超过正确的长度,我会让数据库代码尽可能地处理它。对字段设置大小限制,并在数据绑定控件中向用户显示数据。绑定到字符串字段将TDBEdit正确执行长度限制。设置它,以便直接从数据集中填充记录,并且它始终具有正确的长度。

然后,您需要担心的是来自不属于您的 UI 的某些外部来源的数据进入记录。为此,请使用相同的过程。让导入代码将数据插入数据集中,并让其长度约束为您进行验证。如果引发异常,则拒绝导入。如果没有,那么您有一个有效的数据集行,可用于从中填充记录。

于 2012-12-17T17:02:50.517 回答
5

您问题中的短字符串类型并不能真正保护字符串不超过适当的长度。当您为这些短字符串分配更长的值时,该值会被静默截断。

我不确定您使用的是什么数据库访问方法,但我想它会做同样的事情。即将任何超长字符串截断到最大长度。在这种情况下,没有什么可做的。

如果你的数据库访问方法在你给它一个过长的字符串时抛出一个错误,那么你需要在将值传递给数据库之前截断。

如果您必须明确截断,那么您可以选择在很多地方这样做。我的理念是在最后一刻截断。这就是你受到限制的地方。截断其他任何地方似乎是错误的。这意味着数据库限制正在蔓延到与数据库不明显相关的代码部分。

当然,所有这一切都是基于您要进行静默截断的假设。如果您想在截断的情况下提供用户反馈,那么您将需要确定正确的反馈点在哪里。

于 2012-12-17T17:03:12.910 回答
3

据我了解,我的答案应该是“不要混合图层”。

我怀疑字符串长度是在数据库层级别(列宽)或业务应用程序层(例如验证卡号)指定的。

从“纯Delphi代码”的角度来看,你不应该知道你的string变量有一个最大长度,除非你到达持久层甚至业务层。

使用属性可能是一个想法。但它可能会“污染”源代码,原因与混合层的原因相同。

因此,我建议使用专用的数据建模,在其中指定您的数据期望。然后,在 Delphivar级别,您只需定义一个普通的string. 这正是我们的mORMot框架实现数据过滤和验证的方式:在模型级别,使用一些专用类 - 方便、可扩展和干净。

于 2012-12-17T19:43:46.120 回答
3

如果您只是从 Delphi 7 移植到 XE3,请别管它。此外,虽然“ShortString”可能已被弃用,但如果他们完全删除它,我会吃掉我的帽子,因为有很多代码在没有它的情况下将永远无法重建。ShortString + Records 仍然是指定面向字节的文件记录数据存储的唯一实用方法。Delphi 永远不会删除 ShortString 也不会改变它的行为,这将对现有的 delphi 代码造成破坏。因此,如果您确实必须定义记录并限制它们的长度,并且您确实不希望这些记录支持 Unicode,那么停止使用或停止编写 ShortString 代码的理由为零。话虽如此,我讨厌短字符串和记录文件,希望它们会消失,并且很高兴它们被标记为已弃用。

话虽如此,我完全同意梅森和大卫的观点;我想说,长度检查和验证是表示/验证问题,而 Delphi 的强类型不是处理它们的正确位置或正确方法。如果您需要对您的类设置验证约束,请编写实现约束存储的辅助类(EmployeeName 是一个字符串字段,EmployeeName 具有以下长度限制)。例如,在编辑控件中,这已经是一个属性。在我看来,使用新的绑定系统将 DB 字段映射到可视字段比尝试在代码中静态表达约束要好得多。

用户输入验证和存储是不同的,长度限制应该在您的 GUI 控件中设置,而不是在您的数据结构中。

例如,如果您想要一个 Unicode 宽但长度有限的字符串,您可以使用 UnicodeChar 数组。您甚至可以使用 Delphi 中新的类帮助器方法编写自己的 LimitedString 类。但是这样的方法不是一个可维护和稳定的设计。

如果您的 SQL 数据库有一个用VARCHAR(100)type 声明的字段,并且您希望将用户的输入限制为 100 个字符,您应该在 GUI 层这样做,并且忘记在幕后默默地强加截断(实际上是数据损坏)。

于 2012-12-17T23:12:16.657 回答
1

我遇到了这个问题 - 严重 - 从 Delphi 6 升级到 2009 对于一个程序正在/正在做什么,必须能够将旧的 ASCII 字符串视为单独的 ASCII 字符。

该程序输出 ASCII 文件(甚至没有 ANSI),并具有诸如在最后一个数字上打孔以表示负数的概念。所以文件格式可以说有点回溯了!

在 2009 年第一次构建之后(10 年的代码,好吧,你不知道!)在对单元名称等进行排序之后,实际上报告了数百个错误/非法分配和数据丢失/转换警告......

不管 Delphi 对字符串和字符的后台操作/魔术有多好,我都不够信任它。最后,为了确保一切都恢复原状,我将它们全部重新声明为字节数组,然后相应地更改了代码。

于 2012-12-17T20:20:05.503 回答
1

您尚未指定 delphi 版本,以下是在 delphi 2010 中对我有用的内容:

版本 1

TTestRecordProp = record
private
  FField20: string;
  ...
  FFieldN: string
  procedure SetField20(const Value: string);
public
  property Field20: string read FField20 write SetField20;
  ...
  property FieldN: string ...
end;
...
procedure TTestRecordProp.SetField20(const Value: string);
begin
  if Length(Value) > 20 then
    /// maybe raise an exception?
    FField20 := Copy(FField20, 1, 20)
  else
    FField20 := Value;
end;

版本2

TTestRecordEnsureLengths = record
  Field20: string;
  procedure EnsureLengths;
end;
...
procedure TTestRecordEnsureLengths.EnsureLengths;
begin
  // for each string field, test it's length and truncate or raise exception
  if Length(Field20) > 20 then
    Field20 := Copy(Field20, 1, 20); // or raise exception...
end;

// 在将数据推送到数据库之前,您必须调用 .EnsureLength...

就个人而言,我建议用对象替换记录,然后你可以做更多的技巧。

于 2012-12-18T03:02:50.310 回答