Передайте Generic Record в массив констант - PullRequest
8 голосов
/ 10 мая 2011

Можно ли каким-либо образом передать универсальную запись в массиве аргумента const в вызов функции?

Я хотел бы использовать запись Nullable от Аллена Бауэрав своем роде ORM, использующем "Plain Old Delphi Objects" для сопоставления строк базы данных:

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: невозможно переопределить версию функции" Execute "вызывается с этими аргументами "(перевод с французского: E2250 Aucune версия Surchargée de 'Execute' ne peut être appelée avec ces arguments)

Я мог бы явно преобразовать 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;

Есть ли у вас какие-либо идеи о том, как сделать этот видвозможно ли построить?Я нахожу способ, добавив явное переопределение оператора приведения к Variant в Nullable с использованием RTTI, но это немного сложно, потому что для этого требуется явное приведение к Variant в массиве вызова const:

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;  

Ответы [ 2 ]

5 голосов
/ 10 мая 2011

Как вы сказали, в параметрах array of const в текущем состоянии компилятора не допускается record.

На самом деле, TVarRec.VType значения не имеют vtRecord.

И сам тип Variant не имеет типа varRecord, обрабатываемого Delphi. В мире Windows существует такой тип варианта (например, структуры DotNet отображаются в тип vt_Record в COM), но этот тип варианта не обрабатывается Delphi.

Что возможно, так это передать указатель на typeinfo записи, затем на запись:

case Params[i].VType of
  vtPointer:
  if (i<high(Params)) and (Params[i+1].VType=vtPointer) then
    if Params[i].VPointer = TypeInfo(MyRecord) then begin
      ...
    end;

Возможно, это не чудесный ответ, так как вы используете дженерики ... но, по крайней мере, для начала ... Во всех случаях, вот как я мог бы использовать это с предварительно универсальными компиляторами Delphi. Ключевое слово TypeInfo () уже было достаточно мощным и быстрее, чем «новая» реализация RTTI.

Другая возможность (совместимая с генериками) должна заключаться в добавлении некоторого идентификатора типа записи в содержимое записи. Затем передайте запись как указатель, прочитайте идентификатор и действуйте с ним, как ожидается:

case Params[i].VType of
  vtPointer:
  if Params[i].VPointer <> nil then
  case PRecordType(Params[i].VPointer)^ of
  aRecordType: ...
      ...
  end;

Это можно сделать простым расширением исходного определения Nullable<T>.

Постскриптум:

Использование записей в ORM, возможно, не лучшее решение ...

Модель class является более продвинутой, чем модель record/object в Delphi, и, поскольку ORM касается моделирования объектов, вы должны использовать лучшую доступную модель ООП, IMHO. Опираясь на классы вместо записей, вы можете иметь наследование , информацию о встроенном типе (с помощью метода Class или просто с помощью PPointer(aObject)^), значения, допускающие значения NULL, и виртуальные методы ( что очень удобно для ОРМ). Даже проблема параметров array of const позволит напрямую обрабатывать такие типы vtObject, а также выделенный виртуальный метод класса. ORM, основанный на записях, будет лишь способом сериализации строк таблицы, а не разработки вашего приложения с использованием наилучшей практики ООП. Что касается производительности, выделение памяти для экземпляров class не является проблемой, если сравнивать с потенциальными проблемами копирования записей (функция _CopyRecord RTL - вызывается скрытым образом компилятором, например, для параметров функции - может быть очень медленно ).

Например, в нашем ORM мы обрабатываем свойства динамического массива, даже динамические массивы записей в свойствах (Delphi не создает RTTI для опубликованных свойств записей, это еще одно противоречивое поведение текущего класса реализация). Мы сериализуем записей и динамических массивов в JSON или двоичные файлы со "старым" RTTI, и это работает очень хорошо. Лучшее из двух объектов мира под рукой.

1 голос
/ 10 мая 2011

Благодаря идее Роберта Лава я добавил Implcit Cast в TValue на Nullable и заменил все мои Params: array of const на const Params: array of TValue.

Теперь я могу передавать параметры без приведения типа Variant:

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;

Метод MapParams стал:

procedure TDbContext.MapParams(Q: TUIBQuery; const Params: array of TValue);
const
  { maps to CREATE DOMAIN BOOLEAN AS CHAR(1) DEFAULT 'F' NOT NULL CHECK (VALUE IN ('V', 'F')) }
  BOOL_STR: array[Boolean] of string = ('F', 'V'); 
var
  I: Integer;
begin
  Q.Prepare(True);
  for I := 0 to Q.Params.ParamCount - 1 do
  begin
    if Params[I].IsEmpty then
      Q.Params.IsNull[I] := True
    else
      case Q.Params.FieldType[I] of
      uftChar,
      uftVarchar,
      uftCstring:
      begin
        { Delphi Booleans are tkEnumeration in TValue }
        if Params[I].Kind = tkEnumeration then
          Q.Params.AsString[I] := BOOL_STR[Params[I].AsBoolean]
        else
          Q.Params.AsString[I] := Params[I].ToString;
      end;
      uftSmallint,
      uftInteger:          Q.Params.AsSmallint[I] := Params[I].AsInteger;
      uftFloat,
      uftDoublePrecision : Q.Params.AsDouble[I]   := Params[I].AsExtended;
      uftNumeric:          Q.Params.AsCurrency[I] := Params[I].AsCurrency;
      uftTimestamp:        Q.Params.AsDateTime[I] := Params[I].AsExtended;
      uftDate:             Q.Params.AsDate[I]     := Params[I].AsInteger;
      uftTime:             Q.Params.AsDateTime[I] := Params[I].AsInteger;
      uftInt64:            Q.Params.AsInt64[I]    := Params[I].AsInt64;

      uftUnKnown, uftQuad, uftBlob, uftBlobId, uftArray
    {$IFDEF IB7_UP}
     ,uftBoolean
    {$ENDIF}
    {$IFDEF FB25_UP}
     ,uftNull
    {$ENDIF}:       Assert(False, 'type de données non supporté');
     end;
  end;
end;

Обратите внимание, что я принудительно проверил проверку типов, попросив базу данных описать и принудительно ввести тип входных параметров, и я изменил способ посещения параметров, отдавая приоритет параметрам запроса, поэтомуэто вызовет исключения, когда преобразования невозможны: в базе данных World гораздо меньше доступных типов данных, чем в Delphi World.

...