C# & SQLite - 存储图像
如何将图像/二进制数据插入数据库。
引言
本文旨在演示如何将图像加载到 SQLite 数据库中并检索它们进行查看。它使用 VS2010、C#、.NET4.0 编写,并使用 ADO.NET 提供程序 System.Data.SQLite
连接到 SQLite 数据库。所有这些都在 Windows XP 环境中。
背景
首先,必须获取一些文件并按照规则安装它们
- SQLite ADO.NET 提供程序:http://sqlite.phxsoftware.com
- SQLite 管理器:http://sqliteadmin.orbmu2k.de
- SQLite 管理器:http://www.sqlite.org
SQLite ADO.NET 提供程序:我将包安装到我的“C:\”目录中,并且选择不注册 DLL 文件,因为我只希望将 DLL 文件包含到我的项目中。
使用代码
SQLite
首先,我创建了一个名为 ImageLib.s3db 的新数据库,并添加了一个表和所需的字段。
CREATE TABLE [ImageStore] (
[ImageStore_Id] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[ImageFile] NVARCHAR(20) NULL,
[ImageBlob] BLOB NULL
);
VS2010 - C# - .NET 4.0
接下来,我创建了一个名为 StoringImages 的 VS2010 项目,更改了默认命名空间,并添加了一些文件夹和文件。
- 文件夹:Database
- 文件:StoringImages.s3db
- 属性:复制到输出目录 => 始终复制
- 文件夹:Model
- dBFunctions.cs
- dBHelper.cs
- Image.cs
- ImageHelper.cs
- 文件:System.Data.SQLite.dll
- 属性:复制到输出目录 => 始终复制
- 文件:SQLite.Interop.dll
- 属性:复制到输出目录 => 始终复制
- 表单:DisplayImages
- 这是项目的启动表单
System.Data.SQLite.dll 和 SQLite.Interop.dll 都需要放置在根(项目)StoringImages 的正下方。这确保这两个文件都安装在与项目“*.exe”文件相同的目录中。
模型
在 Model 文件夹中,有几个类,两个用于处理所有数据库事务,两个用于处理图像事务。处理数据库事务的两个类 dBFunctions 和 dBHelper,我之前在我的上一篇文章 C# & SQLite 中使用过。所以接下来,我将解释如何使用剩下的两个类:Image
和 ImageHelper
。
我将使用 Image
类作为自定义变量,它将用于存储导入图像文件的数据,以便在方法之间传递。
完成所有繁重工作的类是 ImageHelper
。在这个类中,你会找到各种处理图像的插入、删除和另存为的方法。Insert
使用另一个名为 LoadImage
的方法,该方法处理图像的二进制读取。Delete
用于从数据库中删除数据。SaveAs
用于将图像保存回所选目录。每次事务后,都会生成一个 事务状态,形式为 isSucces
。视图(表单)DisplayImages
需要此状态才能更新或不更新自身。
ImageHelper - 引用赋值
我尽量不使用比所需更多的引用,但有时会忘记删除 VS2010 自动添加到每个新类中的引用。
using System;
using System.IO;
using System.Windows.Forms;
using System.Data;
using System.Data.SQLite;
ImageHelper - 变量声明
MaxImageSize
用于声明导入图像时允许的最大字节数,在此示例中,它在 LoadImage
方法中被覆盖。
private dBHelper helper = null;
private string fileLocation = string.Empty;
private bool isSucces = false;
private int maxImageSize = 2097152;
//2MB - 2097152
//5MB - 5242880
//10MB - 10485760
/* Conversion
* 1 Byte = 8 Bit
* 1 Kilobyte = 1024 Bytes
* 1 Megabyte = 1048576 Bytes
* 1 Gigabyte = 1073741824 Bytes
* */
dBHelper
是处理数据库事务的类。maxImageSize
用于上传时允许的默认最大字节数。isSucces
告知视图事务 [插入、删除、另存为] 是否成功。
ImageHelper - 属性
private string FileLocation
{
get { return fileLocation; }
set
{
fileLocation = value;
}
}
ImageHelper - GetSucces 方法
此方法由表单 DisplayImage
使用,以查找事务 [插入、删除、另存为] 是否成功。
public Boolean GetSucces()
{
return isSucces;
}
ImageHelper - LoadImage 方法
首先,我们向用户询问所选图像文件的位置 [路径],以便我们可以在 FileStream
中使用它。一旦 Filestream
打开,我们将图像读取为二进制并将在 Image
类的实例中获取的数据存储起来,我们将把它发送给方法 LoadImage
的调用者,即 InsertImage
方法。
private Image LoadImage()
{
//Create an instance of the Image Class/Object
//so that we can store the information
//about the picture an send it back for
//processing into the database.
Image image = null;
//Ask user to select Image
OpenFileDialog dlg = new OpenFileDialog();
dlg.InitialDirectory = @"C:\\";
dlg.Title = "Select Image File";
//dlg.Filter = "Tag Image File Format (*.tiff)|*.tiff";
//dlg.Filter += "|Graphics Interchange Format (*.gif)|*.gif";
//dlg.Filter += "|Portable Network Graphic Format (*.png)|*.png";
//dlg.Filter += "|Joint Photographic Experts Group Format (*.jpg)|*.jpg";
//dlg.Filter += "|Joint Photographic Experts Group Format (*.jpeg)|*.jpeg";
//dlg.Filter += "|Nikon Electronic Format (*.nef)|*.nef";
//dlg.Filter += "|All files (*.*)|*.*";
dlg.Filter = "Image Files (*.jpg ; *.jpeg ; *.png ; *.gif ; *.tiff ; *.nef)
|*.jpg;*.jpeg;*.png;*.gif;*.tiff;*.nef";
dlg.ShowDialog();
this.FileLocation = dlg.FileName;
if (fileLocation == null || fileLocation == string.Empty)
return image;
if (FileLocation != string.Empty && fileLocation != null)
{
Cursor.Current = Cursors.WaitCursor;
//Get file information and calculate the filesize
FileInfo info = new FileInfo(FileLocation);
long fileSize = info.Length;
//reasign the filesize to calculated filesize
maxImageSize = (Int32)fileSize;
if (File.Exists(FileLocation))
{
//Retreave image from file and binary it to Object image
using (FileStream stream = File.Open(FileLocation, FileMode.Open))
{
BinaryReader br = new BinaryReader(stream);
byte[] data = br.ReadBytes(maxImageSize);
image = new Image(dlg.SafeFileName, data, fileSize);
}
}
Cursor.Current = Cursors.Default;
}
return image;
}
ImageHelper - InsertImage 方法
InsertImage
通过 NewPicture
方法从视图(表单)DisplayImages
调用。一旦插入成功完成,它将把新获得的 image_id
返回给视图。
正如你所注意到的,在 InsertImage
和 LoadImage
方法之间使用了 Image 类的一个实例。
public Int32 InsertImage()
{
DataRow dataRow = null;
isSucces = false;
Image image = LoadImage();
//if no file was selected and no image was created return 0
if (image == null) return 0;
if (image != null)
{
// Determin the ConnectionString
string connectionString = dBFunctions.ConnectionStringSQLite;
// Determin the DataAdapter = CommandText + Connection
string commandText = "SELECT * FROM ImageStore WHERE 1=0";
// Make a new object
helper = new dBHelper(connectionString);
{
// Load Data
if (helper.Load(commandText, "image_id") == true)
{
// Add a row and determin the row
helper.DataSet.Tables[0].Rows.Add(
helper.DataSet.Tables[0].NewRow());
dataRow = helper.DataSet.Tables[0].Rows[0];
// Enter the given values
dataRow["imageFileName"] = image.FileName;
dataRow["imageBlob"] = image.ImageData;
dataRow["imageFileSizeBytes"] = image.FileSize;
try
{
// Save -> determin succes
if (helper.Save() == true)
{
isSucces = true;
}
else
{
isSucces = false;
MessageBox.Show("Error during Insertion");
}
}
catch (Exception ex)
{
// Show the Exception --> Dubbel Id/Name ?
MessageBox.Show(ex.Message);
}
}//END IF
}
}
//return the new image_id
return Convert.ToInt32(dataRow[0].ToString());
}
ImageHelper - DeleteImage 方法
DeleteImage
执行从数据库中删除图像的操作。该方法需要一个整数,即数据集的行号,由视图(表单)DisplayImages
通过 DeletePicture
方法提供。处理后,DeleteImage
将“状态”返回给 DeletePicture
。
public void DeleteImage(Int32 imageID)
{
//Set variables
isSucces = false;
// Determin the ConnectionString
string connectionString = dBFunctions.ConnectionStringSQLite;
// Determin the DataAdapter = CommandText + Connection
string commandText = "SELECT * FROM ImageStore WHERE image_id=" + imageID;
// Make a new object
helper = new dBHelper(connectionString);
{
// Load Data
if (helper.Load(commandText, "image_id") == true)
{
// Determin if the row was found
if (helper.DataSet.Tables[0].Rows.Count == 1)
{
// Found, delete row
helper.DataSet.Tables[0].Rows[0].Delete();
try
{
// Save -> determin succes
if (helper.Save() == true)
{
isSucces = true;
}
else
{
isSucces = false;
MessageBox.Show("Delete failed");
}
}
catch (Exception ex)
{
// Show the Exception --> Dubbel ContactId/Name ?
MessageBox.Show(ex.Message);
}
}
}
}
}
ImageHelper - SaveAsImage 方法
最后,我添加了一个 SaveAs
方法。将二进制数据保存回图像文件,保存到用户选择的分配目录。
再次,我们需要知道数据集的哪一行需要保存到文件,因此我们的方法需要一个整数作为参数。
首先,我们将局部变量设置为默认值,这是 C# - .NET 的要求和良好的标准编程实践。
然后,我们通过 SaveDialog 向用户询问新图像的目录位置和文件名。设置了 dialog.Filter
范围,我们允许,并相应执行检查。
二进制数据使用 dBHelper
从数据库中检索,再次使用 Image
类的一个实例。如果 dBHelper.Load
返回“true
”值,则执行 FileStream
并处理将二进制写入图像。为了结束此过程,“状态”isSucces
将返回给视图(表单)DisplayImages
。
public void SaveAsImage(Int32 imageID)
{
//set variables
DataRow dataRow = null;
Image image = null;
isSucces = false;
// Displays a SaveFileDialog so the user can save the Image
SaveFileDialog dlg = new SaveFileDialog();
dlg.InitialDirectory = @"C:\\";
dlg.Title = "Save Image File";
//1
dlg.Filter = "Tag Image File Format (*.tiff)|*.tiff";
//2
dlg.Filter += "|Graphics Interchange Format (*.gif)|*.gif";
//3
dlg.Filter += "|Portable Network Graphic Format (*.png)|*.png";
//4
dlg.Filter += "|Joint Photographic Experts Group Format (*.jpg)|*.jpg";
//5
dlg.Filter += "|Joint Photographic Experts Group Format (*.jpeg)|*.jpeg";
//6
dlg.Filter += "|Bitmap Image File Format (*.bmp)|*.bmp";
//7
dlg.Filter += "|Nikon Electronic Format (*.nef)|*.nef";
dlg.ShowDialog();
// If the file name is not an empty string open it for saving.
if (dlg.FileName != "")
{
Cursor.Current = Cursors.WaitCursor;
//making shore only one of the 7 is being used.
//if not added the default extention to the filename
string defaultExt = ".png";
int pos = -1;
string[] ext = new string[7] {".tiff", ".gif", ".png",
".jpg", ".jpeg", ".bmp", ".nef"};
string extFound = string.Empty;
string filename = dlg.FileName.Trim();
for (int i = 0; i < ext.Length; i++)
{
pos = filename.IndexOf(ext[i], pos + 1);
if (pos > -1)
{
extFound = ext[i];
break;
}
}
if (extFound == string.Empty) filename = filename + defaultExt;
// Determin the ConnectionString
string connectionString = dBFunctions.ConnectionStringSQLite;
// Determin the DataAdapter = CommandText + Connection
string commandText = "SELECT * FROM ImageStore WHERE image_id=" + imageID;
// Make a new object
helper = new dBHelper(connectionString);
// Load the data
if (helper.Load(commandText, "") == true)
{
// Show the data in the datagridview
dataRow = helper.DataSet.Tables[0].Rows[0];
image = new Image(
(string)dataRow["imageFileName"],
(byte[])dataRow["imageBlob"],
(long)dataRow["imageFileSizeBytes"]
);
// Saves the Image via a FileStream created by the OpenFile method.
using (FileStream stream = new FileStream(filename, FileMode.Create))
{
BinaryWriter bw = new BinaryWriter(stream);
bw.Write(image.ImageData);
isSucces = true;
}
}
Cursor.Current = Cursors.Default;
}
if (isSucces)
{
MessageBox.Show("Save succesfull");
}
else
{
MessageBox.Show("Save failed");
}
}
视图 - (表单) DisplayImages
该表单包含一个 splitpanel
,一侧(左侧)有一个图片框,另一侧(右侧)有一个 DataGridView
标签。它还包含一个链接到 DataGridView
的 ContextMenuStrip
。ContextMenuStrip
包含此小项目的三个命令:New
、Delete
和 SaveAs
。
表单本身包含一些额外的处理命令、从数据库检索数据和填充 DataGridView
的方法。填充 DataGridView
只在应用程序启动时以及每次命令成功执行后执行。
备注
我知道 ImageHelper
类及其方法需要重构,但我特意保留了这种方式,以便它包含所有功能;这使得它更容易阅读。
我讨厌阅读关于代码的文章,它们到处都是,跳进跳出方法才能理解。
关注点
那些读过我上一篇文章 C# & SQLite 的人会认出处理所有数据库事务的两个数据库类。