delphi-10.3-rio

Generic function to returning value from query with type T passed parameters


I have this code below, the var name varCampo when returned null causes an "invalid type cast" error when using different versions of Delphi. I have tried 10.3 and 11.2.

In Delphi 10.3, when using, eg:

var codigo: integer;

codigo := RetornaCampoTabela<integer>(['a'], ['teste'], 'description', 'Id');

When executing the code and varCampo = null, Delphi 10.3 returns 0 (zero), which is correct, but in Delphi 11.2 I get "INVALID TYPE CAST".

How to solve this code so it works in all versions of Delphi?

class function TDBUtilsController.RetornaCampoTabela<T>(aCamposWhere: array of string; aValuesWhere: array of Variant; ATabela, ARetorno: string): T;
var
  Statement: IDBStatement;
  ResultSet: IDBResultSet;
  varCampo : Variant;
  sSql, sWhere, saux : string;
  I : Integer;
  valResult: TValue;
begin
  if Length(aValuesWhere) <> Length(aCamposWhere) then
  begin
    Application.MessageBox('Parâmetros não conferem', 'Array inválido', MB_OK + MB_ICONERROR);
    Abort;
  end;

  sSql := 'SELECT ' + ARetorno +  ' AS CAMPO FROM ' +  ATabela +  ' ';
  sWhere := ' WHERE ';
  for i := 0 to  Length( aValuesWhere ) -1 do
  begin
    case VarType(aValuesWhere[i]) of
      varString, varUString: saux := QuotedStr( VarToStr( aValuesWhere[i] ) );
    else
      saux := VarToStr( aValuesWhere[i] );
    end;
    if i > 0 then
      sWhere := sWhere + '  AND ' + aCamposWhere[I] + ' = ' +  saux
    else
      sWhere := sWhere + aCamposWhere[I] + ' = ' +  saux ;
  end;
  sSql := sSql + sWhere;
  try
    Statement := AIDBConn.CreateStatement;
    Statement.SetSQLCommand(sSql );
    ResultSet := Statement.ExecuteQuery;
    varCampo  := ResultSet.GetFieldValue('CAMPO');

    valResult := TValue.FromVariant(varCampo);
    Result    := TValue.From<T>( valResult.AsType<T> ).AsType<T>;
  except
    on E: Exception do
    begin
      raise Exception.Create('Erro ao buscar campo: ' + ARetorno  +
           '  da tabela: ' +  ATabela  + sLineBreak + e.Message );
    end;
  end;
end;

I tried to use case for detecting the datatype T and return according to T passed into the function, eg:

if varIsNull(varCampo) then
begin
  case getTypeInfo(T) of
    tkInteger: Result := TValue.From<Integer>(0)
  end else
  begin
    Result := TValue.From<T>( valResult.AsType<T> ).AsType<T>;
  end

I don't want this type of code, I want generic code.


Solution

  • Why are you involving TValue at all? The compiler already knows how to implicitly convert Variant to various types, including integers. If varCampo is null, just return Default(T) instead, otherwise return the Variant as-is and let the compiler decide how to convert it, eg:

    varCampo := ResultSet.GetFieldValue('CAMPO');
    if VarIsNull(varCampo) then
      Result := Default(T)
    else
      Result := varCampo;
    

    UPDATE: apparently the compiler doesn't like the latter implicit conversion. So, you are right, you will have to go through TValue. However, your usage is too verbose, you don't need to create a 2nd TValue from a value you have already converted from another TValue. Your usage of TValue can be simplified:

    if varIsNull(varCampo) then begin
      Result := Default(T);
    end else
    begin
      valResult := TValue.FromVariant(varCampo);
      Result := valResult.AsType<T>;
    end;
    

    Or simpler:

    if varIsNull(varCampo) then
      Result := Default(T)
    else
      Result := TValue.FromVariant(varCampo).AsType<T>;