Spring Nullable Type: TValue.FromVariant is still plaguing us.

Issue #97 closed
Todd Flora created an issue

We are using Nullable<T> and just taking the Value from our ORACLE Result set and setting it into a class. that uses Nullable<TDateTime> Like So:

Uses Spring;

type
  TCustomerHistoryStatistic = class
  public
    property LastEditDate : Nullable<TDateTime> . . . . 
  end;


Procedure LoadStatistics
var
  query: TQueryWrapper;
begin
  query.SetSQLText(tmpQuery);
  query.Open;

  while not query.EOF do
  begin
    Resource := TCustomerHistoryStatistic.Create;
    Resource.LastEditDate := query.FieldByName(csfModifiedDatetime).Value; 

  end;

This code runs fine but when we attempt to get the value it blows with an invalid variant type because of the Oracle types that are not supported by TValue.FromVariant

The following code in the Spring unit fails

class operator Nullable<T>.Implicit(const value: Variant): Nullable<T>;
var
  v: TValue;
begin
  if not VarIsNullOrEmpty(value) then
  begin
    v := TValue.FromVariant(value);
    Result := Nullable<T>.Create(v.AsType<T>);
  end
  else
    Result.Clear;
end;

Once again I would like to suggest that the FromVariant Utility method in Spring.Persistence.Core.Utils.TUtils should be used everywhere TValue.FromVariant is called.

Comments (8)

  1. Stefan Glienke repo owner

    And why should Nullable<T> know about some special treatment that relates to Oracle? Variants are variants and they can hold all sorts of stuff. That does not mean that everything that knows about Variant and puts them into TValue should try to know how to do that - especially since it just can't.

    Just don't use the implicit overload for Variant in that case and write:

    if not query.FieldByName(csfModifiedDatetime).IsNull then
      Resource.LastEditDate := query.FieldByName(csfModifiedDatetime).AsDateTime;
    

    Does that solve the problem?

    Or just write some conversion function that turns that oracle thing into a regular Variant (as in one that TValue understands) and use that in your code.

    The reason why I am trying to avoid this is that it will lead to handling all sorts of custom variant types when converting to TValue and these Oracle types would be just the beginning. What's next? I don't know how many different custom variant types are out there that could/should be handled in such conversion routine.

    How do these values convert to delphi data types anyway? Does a direct assignment to TDataTime or other types work?

  2. Todd Flora reporter

    No because if the Field coming from the database is null then asDateTime fails. Whats the use of a nullable type then if you have to check for null on the value you are putting into it.

    I agree that it should not be Spring4d's responsibility to handle these variant types. In my opinion it is a shortfall of TValue.FromVariant method, but since Embarcadero is unlikely to fix it any time soon, somehow it will need to be handled. Since the persistence layer already handles it and the Nullable<T> type will likely mostly be used for persistence I just thought that you might want to reconsider providing a solution. Especially since it seems to be a simple solution.

    Plus I don't believe the BCDVariant and SqlTimeStampVariant types are necessarily Oracle specific, we are just seeing them with Oracle. Maybe other databases use them as well. When Marshmallow is released and both it and Nullable type are more widely used by others it is my guess you will be seeing this issue more often reported by others.

    BTW I have looked in XE7 and the fromVariant method has not changed. I will report this issue to Embarcadero as well and see how they respond. Thanks for your time and consideration.

    --

  3. Todd Flora reporter

    Yes And one more

    http://docwiki.embarcadero.com/Libraries/XE7/en/Data.SqlTimSt.TSQLTimeStampOffset http://docwiki.embarcadero.com/Libraries/XE7/en/Data.SqlTimSt.TSQLTimeStampOffsetCreate

    Also I get what you are saying about Variant types and trying to handle all sorts of special types. These types are part of the VCL and should be handled by the TValue.FromVariant method. I would guess that it is an oversight on Embarcadero's part. I have reported this as Embarcadero Bug #130614. We will see what they say.

  4. Stefan Glienke repo owner

    New bugs should go into the new system (quality.embarcadero.com) - those in the old one are more likely to be ignored. Also it's RTL and not VCL.

  5. Stefan Glienke repo owner

    I looked a bit into it and handling this in TValue.FromVariant is not that easy. Since the VType for custom variant type is dynamic you cannot assume that its a fixed number (but you know that from your research of the problem). Also making Spring.Base depend on any unit from dbrtl is not an option.

    So I guess that leaves us with two choices:

    1) hardcoding it the way you solved this in TUtils.FromVariant and being limited to what is being hardcoded there

    • pro: simple enough
    • con: hardcoded and requires changes to Spring.pas for supporting new custom variant types

    2) providing some way of registering converters that handle custom variant to TValue

    • pro: flexible
    • con: more complicated and you need the correct spot to register them

    While I would like to make it flexible enough I guess the first step would be to hardcode the handling of the mentioned three types.

  6. Stefan Glienke repo owner

    I added TValueHelper.FromVariant some while ago which should solve that problem as it is used in the implicit operator of Nullable<T>. Please check if now your code runs properly.

  7. Log in to comment