Hi:
I have experimented with this scenario (storing image BLOBs in a SQL Server varbinary(max) column) and it’s been painful; I have yet to find a satisfactory solution but what I’ve got so far works.
The experiment involves storing images of space objects (stars, nebulae, etc.) using MSSQL’s FILESTREAM feature to have them physically stored in the filesystem rather than inside the table. That way they are easier to manage and backup with standard MSSQL tools; they’re also accessibe (read only) to any .Net applications without going through MSSQL.
ObjImage table:
CREATE TABLE [dbo].[ObjImage](
[ID] [int] IDENTITY(1,1) NOT NULL,
[ObjImageID] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[SpaceObjectID] [int] NOT NULL,
[MimeType] [nvarchar](50) NULL,
[FileSize] [int] NULL,
[FileName] [nvarchar](50) NULL,
[FileContent] [varbinary](max) FILESTREAM NULL,
CONSTRAINT [PK_ObjImage] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] FILESTREAM_ON [FSDBFS],
CONSTRAINT [UQ__ObjImage__41623AAD80EE9DAB] UNIQUE NONCLUSTERED
(
[ObjImageID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] FILESTREAM_ON [FSDBFS]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[ObjImage] WITH CHECK ADD CONSTRAINT [FK_ObjImage_SpaceObject] FOREIGN KEY([SpaceObjectID])
REFERENCES [dbo].[SpaceObject] ([ID])
GO
ALTER TABLE [dbo].[ObjImage] CHECK CONSTRAINT [FK_ObjImage_SpaceObject]
GO
Yii model rules:
public function rules()
{
return array(
array('SpaceObjectID', 'required'),
array('SpaceObjectID, FileSize', 'numerical', 'integerOnly'=>true),
array('MimeType, FileName', 'length', 'max'=>50, 'allowEmpty'=> true),
array('FileContent', 'file','types'=>'jpg, jpeg, png, bmp, pdf, doc, docx, xls, xlsx', 'maxSize'=>(4 * 1024 * 1024), 'allowEmpty'=>true),
array('ID, SpaceObjectID, MimeType', 'safe', 'on'=>'search'),
);
}
SpaceObject is the parent table that contains the object’s information; a record must exist there before one can add the picture.
Yii model save methods:
public function beforeSave()
{
if($file=CUploadedFile::getInstance($this,'FileContent'))
{
$this->FileName=$file->name;
$this->MimeType=$file->type;
$this->FileSize=$file->size;
$this->ObjImageID=new CDbExpression("NEWID()");
}
return parent::beforeSave();
}
public function afterSave()
{
if($file=CUploadedFile::getInstance($this,'FileContent'))
{
$connection=Yii::app()->db;
$sql1 = "DECLARE @img AS VARBINARY(MAX);";
$sql2 = "SELECT @img = CAST(bulkcolumn AS VARBINARY(MAX)) FROM OPENROWSET(BULK '".$file->tempName."',SINGLE_BLOB ) AS BLOB;";
$sql3 = "UPDATE ObjImage SET FileContent = @img WHERE ID = ".$this->ID.";";
$sql = $sql1.$sql2.$sql3;
$command=$connection->createCommand($sql);
$rowCount=$command->execute();
}
parent::afterSave();
}
beforeSave() is particularly needed to seed the ROWGUIDCOL required for the filestream feature at $this->ObjImageID=new CDbExpression(“NEWID()”); because I haven’t found a way to make Yii handle it the same way it handles autoincrement integer primary keys.
afterSave() is my clunky workaround to avoid the character set conversion errors. I tried all kinds of PHP functions (e.g. bin2hex, iconv) for this and couldn’t make them work; MSSQL’s use of UCS2 is simply a pain to handle in UTF8 world. I ended up using a bulk insert (sorry for the 3 SQL statements but I had to build this incrementally so I wouldn’t get lost
)
The controller and views are Gii standard; nothing special there.
This is the code to retrieve and view the picture:
<?php $this->widget('zii.widgets.CDetailView', array(
'data'=>$model,
'attributes'=>array(
'ObjImageID',
'SpaceObjectID',
'FileName',
'FileSize',
'MimeType',
array(
'name'=>'FileContent',
'type'=>'image',
'value'=>'data:'.$model->MimeType.';base64,'.base64_encode(hex2bin($model->FileContent)),
),
),
)); ?>
Hope this helps…