一、 引言
在构建数据驱动的应用程序时,经常需要捕获文本和二进制数据。这样的程序可能需要存储图像,PDF,Word文件或其它二进制数据。能够使用两种方式来存储这些二进制数据:存储在web服务器的文件系统上并添加一个对数据库中相应文件的引用;或直接存储在数据库本身。
文本数据,例如字符串,数字,日期,GUID,货币值,等等-在数据库系统中都有适当的和相应的数据类型定义。例如,在Microsoft SQL Server中,你可以使用int数据类型来存储一个整数值;而为了存储一个字符串值,你可以使用一个varchar或nvarchar类型。另外,数据库还提供了用于存储二进制数据的类型定义。在Microsoft SQL SERVER 2000及早期版本中,使用image数据类型来存储二进制数据;而在SQL SERVER 2005中,使用varbinary(MAX)数据类型。使用上面两种方式中的任何一种,这些数据类型都能够存储可达2GB大小的二进制数据。
不过,当直接把二进制数据存储在数据库时,需要增加一些额外工作来实现插入、更新和检索二进制数据。幸好,我们可以通过更高级的数据存取库-例如ADO.NET-对这种复杂的低级T-SQL操作加以抽象,从而使问题变得相当简单。然而,通过ADO.NET方式使用二进制数据与使用文本数据的确有点不同。在本文中,我们将分析如何使用ADO.NET和ASP.NET 2.0 SqlDataSource控件直接通过一个数据库来存储和检索图像文件。请接着往下阅读!
二、 把数据存储在数据库中与存储在文件系统中的比较
正如刚才介绍的,当捕获一个应用程序中的二进制数据时,该二进制数据既可以直接存储在数据库中也可以作为一个文件保存在web服务器的文件系统中-仅保持一个对数据库中文件的引用。根据我的体验,我发现大多数开发者更喜欢在文件系统中存储二进制数据,这主要基于下列原因:
· 需要较少的工作-存储和检索存储在数据库中的二进制需要更多的编码工作。而且,更新这些二进制数据也会更为容易-不需要与数据库通讯,只须直接修改文件即可!
· 指向文件的URL更为直接-正如我们将在本文中所看到的,为了提供存取存储在一个数据库中的二进制数据,我们需要创建另一个能够返回该数据的ASP.NET页面。典型地,会把相应于数据库中对应记录(返回它的二进制数据)的一个唯一的标识符传递给这个页面。结果是,为了存取该二进制数据-比方说一个上传的图像-该URL看上去如http://www.yourserver.com/ShowImage.aspx?ID=4352的形式,而如果该图像直接存储在文件系统中,URL将更为直接些-例如http://www.yourserver.com/UploadedImages/Sam.jpg?http://www.xvna.com。
· 为显示图像提供更好的工具支持-如果你在使用ASP.NET 2.0,那么,你可以在GridView或DetailsView控件中使用ImageField控件来显示一个图像(它的图像路径存储在数据库中)。然而,遗憾的是,这个ImageField却无法直接显示数据库中的图像数据(既然它要求查询一个外部页面并且返回相应的数据)。
· 性能-既然二进制文件存储在web服务器的文件系统而不是存储在数据库中,那么,应用程序可以访问数据库中较少的数据,从而减少了对数据库的要求,也相应地减少了存在于web和数据库服务器之间的网络拥挤。
把数据直接存储在数据库的主要优点在于,它能够使数据成为"自包含的"。既然所有的数据都包含在数据库中,那么,数据支持、数据在数据库服务器间的移动以及数据库复制等等就容易得多了,因为不存在担心复制或备份存储在文件系统中的二进制内容这样的问题。
如往常一样,至于选择哪种存储方案要具体依赖于实际的使用场所和业务需要。例如,我开发过一个客户端,其中的二进制数据必须存储在数据库中,因为它们使用的报告软件仅能够在报告中包括二进制数据-如果它来自于数据库的话。在另一种情况下,我的一个同事在开发一个工程,其中的二进制文件需要为web应用程序使用并且可经由FTP使用,这种情况很有必要把二进制数据存储在文件系统中。
三、 创建一个存储二进制数据的数据库表格
本文中的其它部分将分析一个简单的ASP.NET2.0图像画廊应用程序,我使用微软SQL Server 2005 Express Edition编写的,用于展示本文所阐述的直接从一个数据库中存储和检索二进制数据的相关概念。
这个图像画廊应用程序的数据模型包括一个表格-Pictures,其中的每一个记录对应画廊中的一幅图片。这个Pictures表格的MIMEType域中存储了上载图像(对于JPG文件是image/jpeg,对于GIF文件是image/gif,等等)的MIME类型;这里的MIME类型向浏览器指定如何生成该二进制数据。其中的ImageData列则存储了该图片实际的二进制内容。
四、 上传一个图像并使用ADO.NET代码存储二进制数据
这个图像画廊允许访问者上传图片文件(GIF,JPG和PNG格式)到这个应用程序中。一旦上传,一个新的记录将被添加到Pictures表格并且该图像文件的内容即被存储在新的记录的ImageData列内。为了实现在ASP.NET 2.0中把文件从web浏览器端上传到web服务器,本示例中使用了FileUpload控件。FileUpload控件的使用方法是很简单的事情-只需要把它从工具栏拖动到你的页面上即可。最终,这个FileUpload控件将在用户的浏览器端生成为标准的文件上传形式-一个"Browse"按钮(当点击它时)允许用户从他们的硬盘中选择一个文件上传到web服务器。
例如,为了创建一个接口以实现添加一个新的图像,我使用一个TextBox控件来捕获图片的标题,还有一个FileUpload控件用于允许用户指定要上传的图像:
<b>Title:</b>
<asp:TextBox ID="PictureTitle" runat="server" />
<br />
<b>Picture:</b>
<asp:FileUpload ID="UploadedFile" runat="server" />
<br />
<asp:LinkButton ID="btnInsert" runat="server" Text="Insert" />
<asp:LinkButton ID="btnCancel" runat="server" Text="Cancel" />
上面的代码创建一个页面,用户能够从他们的硬盘上指定一个要上传到web服务器的文件。
一旦用户选择了一个文件并且寄送了这个表单(例如通过点击"Insert"按钮),那么,指定文件的二进制内容即被寄送到web服务器。从服务器端代码中,这种二进制数据通过FileUpload控件的PostedFile.InputStream属性成为可用,这正如下面的标记和代码所展示的:
Protected Sub btnInsert_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnInsert.Click
'确保一个文件被成功上传
If UploadedFile.PostedFile Is Nothing OrElse String.IsNullOrEmpty(UploadedFile.PostedFile.FileName) OrElse UploadedFile.PostedFile.InputStream Is Nothing Then
... 显示错误信息...
Exit Sub
End If
'确保我们在操作一个JPG或者GIF文件
Dim extension As String = Path.GetExtension(UploadedFile.PostedFile.FileName).ToLower()
Dim MIMEType As String = Nothing
Select Case extension
Case ".gif?http://www.xvna.com"
MIMEType = "image/gif"
Case ".jpg?http://www.xvna.com", ".jpeg", ".jpe"
MIMEType = "image/jpeg"
Case ".png"
MIMEType = "image/png"
Case Else
'无效的文件类型上传
... 显示错误信息...
Exit Sub
End Select
'与数据库连接并且把一条新记录插入到Products表格中
Using myConnection As New SqlConnection(ConfigurationManager.ConnectionStrings("ImageGalleryConnectionString").ConnectionString)
Const SQL As String = "INSERT INTO [Pictures] ([Title], [MIMEType], [ImageData]) VALUES (@Title, @MIMEType, @ImageData)"
Dim myCommand As New SqlCommand(SQL, myConnection)
myCommand.Parameters.AddWithValue("@Title", PictureTitle.Text.Trim())
myCommand.Parameters.AddWithValue("@MIMEType", MIMEType)
'把FileUpload控件的InputStream加载到字节数组中
Dim imageBytes(UploadedFile.PostedFile.InputStream.Length) As Byte
UploadedFile.PostedFile.InputStream.Read(imageBytes, 0, imageBytes.Length)
myCommand.Parameters.AddWithValue("@ImageData", imageBytes)
myConnection.Open()
myCommand.ExecuteNonQuery()
myConnection.Close()
End Using
End Sub
在此,这个事件处理器首先确保已经上传一个文件。然后,它根据被上传的文件的扩展名来决定MIME类型。
上面最值得注意的是那些设置@ImageData参数的代码部分。首先,创建一个名为imageBytes的字节数组并且使其长度为被上传的文件相应的InputStream。然后,从InputStream中使用Read方法把二进制内容填入这个字节数组。注意,正是这个字节数组被指定为@ImageData的值。