在 const 数组中传递通用记录
是否可以以任何方式将 const 参数数组中的通用记录传递给函数调用?
我想在一种自制的 ORM 中使用 Allen Bauer 的 Nullable 记录,使用“普通旧 Delphi 对象”来映射数据库行:
type
Nullable<T> = record
...
end;
TMyTableObject = class(TDbOject)
private
FId: Integer;
FOptionalField: Nullable<string>;
protected
procedure InternalSave; override;
public
property Id: Integer read FId write SetId;
property OptionalField: Nullable<string> read FOptionalField write SetOptionalField;
end;
...
implementation
procedure TMyTableObject.InternalSave;
begin
{ FDbController is declared and managed in TDbObject, it contains fonction to run queries
on the database }
FDbController.Execute(
'update or insert into MY_TABLE(TABLE_ID, OPTIONAL_FIELD) ' +
'values (?, ?) ' +
'matching(TABLE_ID) returning TABLE_ID', [FId, FOptionalField],
procedure (Fields: TSQLResult)
begin
FId := Fields.AsInteger[0];
end;
end;
end;
上面的代码会导致错误:“E2250:无法使用这些参数调用“执行”函数的重写版本”(翻译自法语) :E2250 Aucune 版本 surchargée de 'Execute' ne peut être appelée avec ces argument)
我可以显式地将 FOptionalField 转换为字符串或其他任何内容,因为 Nullable 覆盖临时运算符,但我确实必须知道 Nullable 是否有值,直到我将 const 数组映射到数据库查询对象的 Params 字段:
procedure TDbContext.MapParams(Q: TUIBQuery; Params: TConstArray);
const
BOOL_STR: array[Boolean] of string = ('F', 'V');
var
i: Integer;
begin
for i := 0 to High(Params) do
case Params[i].VType of
vtInteger : Q.Params.AsInteger[i] := Params[i].VInteger;
vtInt64 : Q.Params.AsInt64[i] := Params[i].VInt64^;
vtBoolean : Q.Params.AsString[i] := BOOL_STR[Params[i].VBoolean];
vtChar : Q.Params.AsAnsiString[i] := Params[i].VChar;
vtWideChar: Q.Params.AsString[i] := Params[i].VWideChar;
vtExtended: Q.Params.AsDouble[i] := Params[i].VExtended^;
vtCurrency: Q.Params.AsCurrency[i] := Params[i].VCurrency^;
vtString : Q.Params.AsString[i] := string(Params[i].VString^);
vtPChar : Q.Params.AsAnsiString[i] := Params[i].VPChar^;
vtAnsiString: Q.Params.AsAnsiString[i] := AnsiString(Params[i].VAnsiString);
vtWideString: Q.Params.AsUnicodeString[i] := PWideChar(Params[i].VWideString);
vtVariant : Q.Params.AsVariant[i] := Params[i].VVariant^;
vtUnicodeString: Q.Params.AsUnicodeString[i] := string(Params[i].VUnicodeString);
vtPointer :
begin
if Params[i].VPointer = nil then
Q.Params.IsNull[i] := True
else
Assert(False, 'not nil pointer is not supported');
end
else
Assert(False, 'not supported');
end;
end;
您知道如何使这种构造成为可能吗?我找到了一种方法,通过使用 RTTI 在 Nullable 中向 Variant 添加显式强制转换运算符覆盖,但这有点棘手,因为它需要在 const 调用数组中显式强制转换为 Variant :
class operator Nullable<T>.Explicit(Value: Nullable<T>): Variant;
begin
if Value.HasValue then
Result := TValue.From<T>(Value.Value).AsVariant
else
Result := Null;
end;
...
FDbController.Execute(
'update or insert into MY_TABLE(TABLE_ID, OPTIONAL_FIELD) ' +
'values (?, ?) ' +
'matching(TABLE_ID) returning TABLE_ID', [FId, Variant(FOptionalField)],
procedure (Fields: TSQLResult)
begin
FId := Fields.AsInteger[0];
end;
end;
Is it possible, in any way, to pass a Generic Record in an array of const argument to a function call ?
I would like to use the Nullable record from Allen Bauer in a kind of home made ORM using "Plain Old Delphi Objects" to map database rows :
type
Nullable<T> = record
...
end;
TMyTableObject = class(TDbOject)
private
FId: Integer;
FOptionalField: Nullable<string>;
protected
procedure InternalSave; override;
public
property Id: Integer read FId write SetId;
property OptionalField: Nullable<string> read FOptionalField write SetOptionalField;
end;
...
implementation
procedure TMyTableObject.InternalSave;
begin
{ FDbController is declared and managed in TDbObject, it contains fonction to run queries
on the database }
FDbController.Execute(
'update or insert into MY_TABLE(TABLE_ID, OPTIONAL_FIELD) ' +
'values (?, ?) ' +
'matching(TABLE_ID) returning TABLE_ID', [FId, FOptionalField],
procedure (Fields: TSQLResult)
begin
FId := Fields.AsInteger[0];
end;
end;
end;
The above code results in an error : "E2250 : no overriden version of the "Execute" function can be called with these arguments" (translated from French : E2250 Aucune version surchargée de 'Execute' ne peut être appelée avec ces arguments)
I could explicitly convert FOptionalField to string or anything else since Nullable overrides ad-hoc operators but I really have to know if the Nullable has a value or not until I map the array of const into the Params field of the database query object :
procedure TDbContext.MapParams(Q: TUIBQuery; Params: TConstArray);
const
BOOL_STR: array[Boolean] of string = ('F', 'V');
var
i: Integer;
begin
for i := 0 to High(Params) do
case Params[i].VType of
vtInteger : Q.Params.AsInteger[i] := Params[i].VInteger;
vtInt64 : Q.Params.AsInt64[i] := Params[i].VInt64^;
vtBoolean : Q.Params.AsString[i] := BOOL_STR[Params[i].VBoolean];
vtChar : Q.Params.AsAnsiString[i] := Params[i].VChar;
vtWideChar: Q.Params.AsString[i] := Params[i].VWideChar;
vtExtended: Q.Params.AsDouble[i] := Params[i].VExtended^;
vtCurrency: Q.Params.AsCurrency[i] := Params[i].VCurrency^;
vtString : Q.Params.AsString[i] := string(Params[i].VString^);
vtPChar : Q.Params.AsAnsiString[i] := Params[i].VPChar^;
vtAnsiString: Q.Params.AsAnsiString[i] := AnsiString(Params[i].VAnsiString);
vtWideString: Q.Params.AsUnicodeString[i] := PWideChar(Params[i].VWideString);
vtVariant : Q.Params.AsVariant[i] := Params[i].VVariant^;
vtUnicodeString: Q.Params.AsUnicodeString[i] := string(Params[i].VUnicodeString);
vtPointer :
begin
if Params[i].VPointer = nil then
Q.Params.IsNull[i] := True
else
Assert(False, 'not nil pointer is not supported');
end
else
Assert(False, 'not supported');
end;
end;
Do you have any idea on how to make this kind of construct possible ? I find a way by adding an explicit cast operator override to Variant in Nullable using RTTI but that's a bit tricky because it needs an explicit cast to Variant in the array of const call :
class operator Nullable<T>.Explicit(Value: Nullable<T>): Variant;
begin
if Value.HasValue then
Result := TValue.From<T>(Value.Value).AsVariant
else
Result := Null;
end;
...
FDbController.Execute(
'update or insert into MY_TABLE(TABLE_ID, OPTIONAL_FIELD) ' +
'values (?, ?) ' +
'matching(TABLE_ID) returning TABLE_ID', [FId, Variant(FOptionalField)],
procedure (Fields: TSQLResult)
begin
FId := Fields.AsInteger[0];
end;
end;
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
正如您所说,在编译器的当前状态下,
const
参数数组中不允许有记录
。事实上,
TVarRec.VType
值没有任何 vtRecord。而且
Variant
类型本身没有 Delphi 处理的varRecord
类型。 Windows 世界中存在这样一种变体类型(例如,DotNet 结构体在 COM 中映射为vt_Record
类型),但 Delphi 不处理这种变体。可能的方法是将指针传递给记录的类型信息,然后传递给记录:
也许这不是一个很好的答案,因为您使用的是泛型......但至少是一个开始......在所有情况下,这就是我可以使用的方式这与预通用 Delphi 编译器有关。 TypeInfo() 关键字已经非常强大,并且比“新”RTTI 实现更快。
另一种可能性(与泛型兼容)应该是在记录内容中添加一些记录类型 ID。然后将记录作为指针传递,读取 ID,并按预期操作:
这可以通过初始
Nullable
定义的简单扩展来实现。Post-Scriptum:
在 ORM 中使用记录可能不是最好的解决方案...
class
模型比record/object
更先进Delphi 中的模型,并且由于 ORM 是关于对象建模的,因此您应该使用可用的最佳 OOP 模型,恕我直言。通过依赖类而不是记录,您可以拥有继承、嵌入类型信息(通过Class
方法或仅通过PPointer(aObject)^
)、可为 null 的值和虚拟方法(这对于 ORM 来说非常方便)。即使是 const 参数数组问题也将允许直接处理此类 vtObject 类型和专用的类虚拟方法。基于记录的 ORM 只是序列化表行的一种方式,而不是使用 OOP 最佳实践来设计应用程序。关于性能,与复制记录的潜在问题(_CopyRecord
RTL 函数 - 由编译器以隐藏方式调用,例如对于函数参数 - 可能会非常慢)。例如,在我们的 ORM 中,我们处理动态数组属性,甚至是属性中记录的动态数组< /a> (Delphi 不会为记录的已发布属性生成 RTTI,这是当前类实现的另一个不一致行为)。我们使用“旧”RTTI 将记录和动态数组序列化为 JSON 或二进制文件,而且效果很好。两个对象世界中最好的。
As you stated, there is no
record
allowed in thearray of const
parameters, in the current state of the compiler.In fact,
TVarRec.VType
values doesn't have any vtRecord.And the
Variant
type itself doesn't have avarRecord
type handled by Delphi. There is such a variant type in the Windows world (for example, DotNet structs are mapped intovt_Record
type in COM), but this kind of variant is not handled by Delphi.What is possible is to pass a pointer to the typeinfo of the record, then to the record:
Perhaps a not wonderful answer since you are using generics... but at least a start... In all cases, that's how I could use this with pre-generic Delphi compilers. The TypeInfo() keyword was already quite powerful, and faster than the "new" RTTI implementation.
Another possibility (compatible with generics) should be to add some record type ID in the record content. Then pass the record as pointer, read the ID, and act with it as expected:
This could be made with simple extending of the initial the
Nullable<T>
definition.Post-Scriptum:
Using records in an ORM is not perhaps the best solution...
The
class
model is more advanced than therecord/object
model in Delphi, and since an ORM is about object modeling, you should use the best OOP model available, IMHO. By relying on classes instead of records, you can have inheritance, embedded type information (via theClass
method or just viaPPointer(aObject)^
), nullable values, and virtual methods (which is very handy for an ORM). Even thearray of const
parameters problem will allow direct handling of suchvtObject
types, and a dedicated class virtual method. An ORM based on records will be only a way of serializing table rows, not designing your application using OOP best practice. About performance, memory allocation ofclass
instances is not a problem, when compared to potential isssues of copying records (the_CopyRecord
RTL function - called in an hidden manner by the compiler e.g. for function parameters - can be very slow).For instance, in our ORM, we handle dynamic array properties, even dynamic arrays of records in properties (Delphi doesn't produce RTTI for published properties of records, that's another inconsistent behavior of the current class implementation). We serialize records and dynamic arrays into JSON or binary with the "old" RTTI, and it works very well. Best of both object worlds at hand.
感谢 Robert Love 的想法,我在 Nullable 上向 TValue 添加了隐式转换,并将所有
Params: array of const
替换为const Params: array of TValue
。我现在可以在不进行变体转换的情况下传递参数:
MapParams 方法已变为:
请注意,我通过要求数据库描述并强制输入参数的类型来强制执行类型检查,并且我反转了访问参数的方式,优先考虑查询参数,因此它会引发无法进行转换时的例外:数据库 World 中可用的数据类型比 Delphi World 中的可用数据类型少得多。
Thanks to Robert Love idea, I added Implcit Cast to TValue on Nullable and replaced all my
Params: array of const
byconst Params: array of TValue
.I can now pass params without Variant casting :
The MapParams method has become :
Note that I enforced type checking by asking the database to describe and force type of input params and I reversed the way params are visited giving priority to query params so it will raise exceptions when conversions are not possible : there is far less datatypes available in the database World than in the Delphi World.