Мир InterBase

Работа с BLOB-полями


Достаточно часто желательно хранить в базе данных разнообразные неструктурированные данные: изображения, OLE-объекты, звук и т. д. Специально для этих целей существует специальный тип данных - BLOB Продемонстрируем использование BLOB-полей на примере простого приложения (см. рис. 2.66), использующего следующую таблицу:

CREATE TABLE BIOLIFE (
ID INTEGER NOT NULL,
CATEGORY VARCKAR (15) character set WIN1251 collate
WIN1251,
COMMON_NAME VARCHAR (30) character set WIN1251 collate 
WIN1251,
SPECIES_NAME VARCHAR (40) character set WIN1251 collate 
WIN1251,
LENGTH_CM_ DOUBLE PRECISION,
LENGTH_IN DOUBLE PRECISION,
NOTES BLOB sub_type 1 segment size 80,
GRAPHIC BLOB sub_type 0 segment size 80);

Для вывода изображений, сохраненных в поле GRAPHIC, мы будем использовать стандартный компонент DBIMagel: TDBImage. Очевидно также, что запросы при работе с BLOB-полями ничем не отличаются от запросов со стандартными типами полей:

SelectSQL: 
SELECT * FROM BIOLIFE
UpdateSQL:
UPDATE BIOLIFE Set
ID=?NEW_ID,
CATEGORY=?NEW_CATEGORY,
COMMON_NAME=?NEW_COMMON_NAME,
SPECIES_NAME=?NEW_SPECIES_NAME,
LENGTH__CM_=?NEW_LENGTH_CM_,
LENGTH_IN=?NEW_LENGTH_IN,
NOTES=?NEW_NOTES,
GRAPHIC=?NEW_GRAPHIC 
WHERE ID=?OLD_ID

Рис 2.66. Внешний вид формы приложения для работы с BLOB-полями 

InsertSQL:
INSERT INTO BIOLIFE(
ID,
CATEGORY,
COMMON_NAME,
SPECIES_NAME,
LENGTH__CM_,
LENGTH_IN,
NOTES,
GRAPHIC 

VALUES (
?NEW_ID,
?NEW_CATEGORY, 
?NEW_COMMON_NAME, 
?NEW_SPECIES_NAME, 
?NEW_LENGTH_CM_, 
?NEW_LENGTH_IN, 
?NEW_NOTES , 
?NEW_GRAPHIC
)
DeleteSQL:
DELETE FROM BIOLIFE 
WHERE ID=?OLD_ID
RefreshSQL:
SELECT * FROM BIOLIFE
WHERE
ID=?OLD_ID

Единственным отличием от обычных типов данных является то, что для присвоения значения BLOB-параметру необходимо использовать потоки (специализированные потомки стандартного класса TStream). Например, если мы хотим сохранить в нашем поле изображение из внешнего файла, то мы можем написать следующий обработчик нажатия на кнопку:


procedure TMainForm.OpenBClick(Sender: ТОbjесt); 
var S. TStream;
FileS: TFileStream; 
begin
if not OpenD.Execute then exit; 
pFIBDataSetl.Edit;
 S : =
pFIBDataSetl.CreateBlobStream(pFIBDataSetl.FieldByName('GRAPHIC') , bmReadWrite);
try
FileS := TFiIeStream.Create(OpenD.FileName, fmOpenRead); 
S.CopyFrom(FileS, FileS.Size); 
finally
FileS.Free; 
S.Free;
pFIBDataSetl.Post;
end;
end;

Обратите внимание на важный момент - перед тем как присваивать значение BLOB-параметру, необходимо перевести pFIBDataSet в состояние редактирования данных. В данном случае это делается безусловным вызовом метода Edit. Вызов метода CreateBlobStream создает экземпляр специального внутреннего класса TFIBDSBlobStream. Скорее всего, вам не придется использовать этот класс напрямую. В нашем примере он нужен только для обмена данными между BLOB-параметром и потоком, который читает данные из файла с изображением. Параметр bmReadWrite означает, что мы собираемся изменять содержимое параметра. Поток S напрямую связан с BLOB-параметром, поэтому, копируя данные из файла (FileS) в поток S, мы фактически присваиваем значение параметру. Остается только сохранить изменения вызовом метода Post Аналогичным образом мы можем сохранить значение BLOB-поля в некоторый внешний файл:

procedure TMainForm.SaveBClick(Sender: TObject); 
var S: TStream;
FileS: TFiIeStream; 
begin
if not SaveD.Execute then exit;
if not pFIBDatasetl.FieldByName('GRAPHIC').IsNull then begin
S : =
pFIBDatasetl.CreaceBlobStream(pFIBDatasetl.FieldByName('GRAPHIC '), bmRead); 
try
if FileExists(SaveD.FileName) then
FileS := TFileStream.Create(SaveD.FileName, fmOpenWrite) 
else
FileS := TFileStream.Create(SaveD.FileName, fmCreate); 
FileS.CopyFrom(S, S.Size); 
finally  S.Free;
FileS.Free; 
end; 
end;
  end;

Обратите внимание, что в этой процедуре мы используем параметр bmRead при создании потока S. Очевидно, что для сохранения содержимого BLOB-поля в файл нам не нужно изменять само поле, поэтому мы создаем поток только для чтения. Еще более простым способом мы можем очистить содержимое BLOB-поля:



procedure TMainForm.ButtonlClick(Sender: TObject); 
begin
pFIBDataSetl .Edit;
pFIBDataSetl.FieldByName('GRAPHIC').Clear;
pFIBDataSetl.Post; 
end;

В этом случае даже не требуется создавать какие-либо потоки. Иногда также нужно знать, является ли BLOB-поле пустым или нет. При использовании визуальных компонентов типа TDBImage мы не можем быть в этом уверены. Согласитесь, что никто не мешает нам "нарисовать" пустую картинку и сохранить ее в BLOB-поле В этом случае мы не сможем отличить "на глаз": есть ли какое-то изображение в BLOB-поле или нет. Однако мы можем написать обработчик события OnDataChange компонента DataSourcel: TDataSource: 

procedure TMainForm.DataSourcelDataChange(Sender: TObject; 
Field: TField) ; 
begin
CheckBoxl.Checked :=
pFIBDataSetl.FieldByName('GRAPHIC').IsNull; 
end;

Это событие вызывается, в частности, при навигации по DBGridl; таким образом, мы всегда можем узнать, является ли текущее поле пустым или нет.
Если вы используете TpFIBQuery для работы с BLOB-полями, то общий принцип остается тем же - необходимо использовать потоки, однако, в отличие от TpFIBDataSet, вам не потребуется создавать какие-то специальные потоки. Например, мы можем написать следующую процедуру, которая сохранит в файлы все изображения из нашей таблицы: 

pFIBQuery.SQL: SELECT * FROM BIOLIFE
procedure TMainForm.Button2Click(Sender: TObject);
var SaveFile: TFileStream;
Index: Integer; 
begin
with pFIBQueryl do begin ExecQuery; 
Index := 1; 
while not Eof do begin
SaveFile := TFileStream.Create(IntToStr(Index) + '.bmp', 
fmCreate);
FN('GRAPHIC').SaveToStream(SaveFile); 
Next;
inc(Index); 
SaveFile.Free; 
end; 
Close; 
end; 
end;

Метод FN является аналогом FieldByName.

Смысл кода, приведенного выше, совершенно очевиден: мы получаем все записи из таблицы BIOLIFE, в цикле получаем от сервера очередную запись из запроса, создаем файл при помощи потока SaveFile, сохраняем в него значение поля GRAPHIC и запрашиваем следующую запись при помощи метода Next. Аналогичным образом мы могли бы присваивать значение BLOB-параметру: pFIBQuery.SQL: INSERT INTO BIOLIFE (GRAPHIC) VALUES (7GRAPHIC)

procedure TMainForm.Button2Click(Sender: TObject); 
var SaveFile: TFileStream;
Index: Integer; 
begin
with pFIBQueryl do begin  Prepare; 
for Index := 1 to 3 do begin
SaveFile := TFileStream.Create(IntToStr(Index) + '.bmp', fmOpenRead);
Params[0].LoadFromStream(SaveFile); 
SaveFile.Free; 
ExecQuery; 
end;
Transaction.Commit; 
end; 
end;

Данный пример вставляет три новые записи в таблицу BIOLIFE и сохраняет в них изображения из некоторых файлов "l.bmp", "2.bmp" и "3.bmp".

Поскольку в данном примере для сохранения изменений использовался метод Commit, то необходимо перезапустить приложение, чтобы увидеть вставленные записи в DBGridl.


Содержание раздела