Problem when returning data from Blob fields with TMemoryStream

Issue #225 invalid
Ezequiel Juliano Müller created an issue

I'm having trouble dealing with Blob fields. I am using FireDAC with PostgreSQL. The field in the database is of type bytea.

If I create an entity with a mapped property as follows:

[Column('cod_arquivo', [cpRequired, cpNotNull])]
fArquivo: Lazy<TMemoryStream>;

By persisting the information, for example with TSession, the process occurs correctly as the data is stored in the field:

fSession.Save(entity);

But when I try to retrieve the entity, the field comes empty (even the data existing in the database field):

fSession.FindOne<TEntity>(1);

//entity.Arquivo is empty

Here is the complete mapping of the entity:

  [Entity]
  [Table('doc_contrato_documento')]
  [Sequence('seq_cod_id', 1, 1)]
  TContratoDocumento = class(TEntity)
  private
    [AutoGenerated]
    [UniqueConstraint]
    [Column('cod_id', [cpPrimaryKey, cpRequired, cpNotNull])]
    fId: Int64;

    [Column('cod_tipo', [cpRequired, cpNotNull])]
    fTipo: TTipoDocumentoContrato;

    [Column('cod_numero', [cpRequired, cpNotNull])]
    fNumero: string;

    [Column('cod_datahora', [cpRequired, cpNotNull])]
    fDataHora: TDateTime;

    [Column('cod_arquivo', [cpRequired, cpNotNull])]
    fArquivo: Lazy<TMemoryStream>;

    [Column('cod_con_id', [cpRequired, cpNotNull])]
    [ForeignJoinColumn('cod_con_id', 'doc_contrato', 'con_id', [fsOnUpdateCascade, fsOnDeleteCascade])]
    fContratoId: Int64;

    [Column('cod_nome', [cpRequired, cpNotNull], 100)]
    fNome: string;

    [Column('cod_register', [cpRequired, cpNotNull])]
    [RegisterColumn]
    fRegister: TDateTime;

    [Column('cod_change', [cpRequired, cpNotNull])]
    [ChangeColumn]
    fChange: TDateTime;

    [Version('cod_version', 1)]
    fVersion: Int64;

    function GetArquivo: TMemoryStream;
    procedure SetArquivo(const value: TMemoryStream);
  public
    constructor Create;
    destructor Destroy; override;

    property Id: Int64 read fId;
    property ContratoId: Int64 read fContratoId write fContratoId;
    property Tipo: TTipoDocumentoContrato read fTipo write fTipo;
    property Numero: string read fNumero write fNumero;
    property DataHora: TDateTime read fDataHora write fDataHora;
    property Nome: string read fNome write fNome;
    property Arquivo: TMemoryStream read GetArquivo write SetArquivo;

    property Register: TDateTime read fRegister write fRegister;
    property Change: TDateTime read fChange write fChange;
    property Version: Int64 read fVersion;
  end;

{ TContratoDocumento }

constructor TContratoDocumento.Create;
begin
  inherited Create;
  fRegister := Now;
end;

destructor TContratoDocumento.Destroy;
begin
  if fArquivo.IsValueCreated then
    fArquivo.Value.Free;
  inherited Destroy;
end;

function TContratoDocumento.GetArquivo: TMemoryStream;
begin
  if not fArquivo.IsValueCreated then
    fArquivo := TMemoryStream.Create;
  Result := fArquivo;
end;

procedure TContratoDocumento.SetArquivo(const value: TMemoryStream);
begin
  if Assigned(value) then
  begin
    value.Seek(0, TSeekOrigin.soBeginning);
    value.Position := 0;
    GetArquivo.Clear;
    GetArquivo.CopyFrom(value, value.Size);
  end;
end;

Comments (2)

  1. Stefan Glienke repo owner

    Your getter is wrong. IsValueCreated tells you if the internal factory for the lazy has done its work yet or not. And of course when you fetch the entity it has not. So the first time you access the property it should run the lazy fetching from the database.

    Look into the ORM tests specifically the TSessionTest.GetLazyStream and the TCustomer class how it is done.

  2. Log in to comment