Blog

All Blog Posts  |  Next Post  |  Previous Post

Crash Course TMS Aurelius – Blobs

Thursday, February 28, 2013

Using blobs in Aurelius is very straightforward and yet very powerful. In summary, all you have to do is declare your field/property as TBlob (declared in unit Aurelius.Types.Blob.pas). This is enough to map it to an existing blob field in your table, and you will be able to save/load the blob content is several many ways. Consider the following mapping:
  [Entity, Automapping]
  TCustomer = class
  private
    FId: integer;
    FName: string;
    FDocument: TBlob;
    [Column('Photo', [TColumnProp.Lazy])]
    FPhoto: TBlob;
    [Column('Descr_Field', [], 65536)]
    FDescription: string;
  public
    property Id: integer read FId write FId;
    property Name: string read FName write FName;
    property Document: TBlob read FDocument write FDocument;
    property Photo: TBlob read FPhoto write FPhoto;
    property Description: string read FDescription write FDescription;
  end;
We have declared three blob properties in our class: Document, Photo and Description. I have used those to show slightly different ways of using blobs. Document and Photo are declared as TBlob, which is the recommended way. Alternatively, Description is declared as string, but manually mapped to database using a size greater than 65535. This tells Aurelius to also consider this field as a blob (memo/cblob to be more specific) instead of VarChar. You could also declare the property as a dynamic array of byte, but it's not recommended, since you gain nothing from doing it. Note that I have also used an unusual field name for Description (Descr_Field) just to show you how manual mapping works.

There is another interesting feature about blobs: Photo is declared as lazy (TColumnProp.Lazy). This indicates that Aurelius will not bring the blob from database when Customer data is retrieved. The blob is only retrieved when your code explicitly reads the content of Photo property.

The following code shows different ways of dealing with blobs (saving and loading):
function SaveCustomerWithBlobs(Manager: TObjectManager): integer;
var
  Customer: TCustomer;
begin
  Customer := TCustomer.Create;
  Customer.Name := 'John';

  Customer.Photo := TFile.ReadAllBytes('picture.bmp');
  Customer.Document.AsBytes := TFile.ReadAllBytes('document.pdf');
  Customer.Description := TFile.ReadAllText('description.txt');

  Manager.Save(Customer);
  Result := Customer.Id;
end;

procedure LoadCustomerAndExportBlobs(Manager: TObjectManager; CustomerId: integer);
var
  Customer: TCustomer;
begin
  Customer := Manager.Find<TCustomer>(CustomerId);

  TFile.WriteAllText('description2.txt', Customer.Description);
  TFile.WriteAllBytes('document2.pdf', Customer.Document);
  TFile.WriteAllBytes('picture2.bmp', Customer.Photo.AsBytes);
end;
As you can see, you can use TBlob.AsBytes property explicitly, or rely on the implicit conversion to TBytes. You could also use AsString property and streams:
  Customer.Photo := TBlob.Create(TFile.ReadAllBytes('picture.bmp'));
  Customer.Document.AsString := 'Some document';
  Stream := TFile.Open('picture.bmp', TFileMode.fmOpen);
  Customer.Photo.LoadFromStream(Stream);
  Stream.Free;
TBlob type also offers methods like IsNull, Clear, and also provides direct access to raw data to improve performance if needed.

As a final note: we have added TColumnProp.Lazy to the Photo blob. We can verify if the blob is loaded using the Loaded property. We can change LoadCustomerAndExportBlobs function to check it:
  Customer := Manager.Find<TCustomer>(CustomerId);

  Assert(Customer.Document.Loaded);
  TFile.WriteAllBytes('document2.pdf', Customer.Document);

  Assert(not Customer.Photo.Loaded);
  TFile.WriteAllBytes('picture2.bmp', Customer.Photo.AsBytes);
  Assert(Customer.Photo.Loaded);
After TCustomer instance is loaded, Document property is loaded, but Photo is not loaded, because we set it as lazy. After we access content of Photo to save it to picture2.bmp file, Photo.Loaded becomes true. You can also see it happening when you check the generate SQL statements:
CREATE TABLE CUSTOMER (
  ID INTEGER NOT NULL,
  NAME VARCHAR(255) NOT NULL,
  DOCUMENT BLOB,
  Photo BLOB,
  Descr_Field BLOB SUB_TYPE TEXT,
  CONSTRAINT PK_CUSTOMER PRIMARY KEY (ID));

CREATE GENERATOR SEQ_CUSTOMER;

SELECT GEN_ID(SEQ_CUSTOMER, 1)
FROM RDB$DATABASE;

INSERT INTO CUSTOMER (
  ID, NAME, DOCUMENT, Photo, Descr_Field)
VALUES (
  :A_ID, :A_NAME, :A_DOCUMENT, :A_Photo, :A_Descr_Field);

SELECT A.ID AS A_ID, A.NAME AS A_NAME, A.DOCUMENT AS A_DOCUMENT, A.Descr_Field AS A_Descr_Field
FROM CUSTOMER A
WHERE  A.ID = :p_0;

SELECT A.Photo As f0_
FROM CUSTOMER A
WHERE  A.ID = :p_0;
The first statement shows how the table is created (Firebird in this example). The INSERT statement creates the customer in the database, and saves the blobs. The first SELECT statement retrieves only Document and Description, but not Photo. The last SELECT statement is executed later, to retrieve the content of Photo field, only when the content of Photo property is read from code.

Wagner Landgraf




This blog post has received 2 comments.


1. Tuesday, November 25, 2014 at 4:35:28 AM

Hi all,

Could you pleas provide source example for saving blob image from a TImage component.
Also loading Blob content to a TImage component

Thanks

Kenzo

kenzo


2. Tuesday, November 25, 2014 at 10:44:53 AM

Please contact us directly for support (see our Support menu option at our site). Basically you can use AsBytes property to read/write the content as byte array.

Wagner Landgraf




Add a new comment

You will receive a confirmation mail with a link to validate your comment, please use a valid email address.
All fields are required.



All Blog Posts  |  Next Post  |  Previous Post