概要  在开发一个与数据打交道的应用程序中,我们常常需要对文本数据和二进制数据进行相关存储操作,例如存储图片,PDF文档,Word文档或其它二进制数据.存储这样的数据一般可以有两种方法:一是在web服务器上存储文件,将其文件的路径写入数据库中,一是直接将二进制数据存在数据库中.

  像字符,数值,货币型这样的一些数据在数据库系统中都可以找到合适和对应的数据类型进行定义.例如在Microsoft SQL Server中要存储一个整型的数值,你可以使用int数据类型来定义,要存储一个string字符型的数据,你可以采用varchar或nvarchar来定义.当在,数据库同样有相关的数据类型来存储二进制数据.在Microsoft SQL Server2000及以前的版本中,可以采用image数据类型,而在SQL Server2005中,则有varbinary(Max)数据类型.在这两个版本中,image和varbinary都可以存储高达2GB大小的容量.

  当将二进制数据存储在数据库时,我们需要在插入、更新等操作做一点点额外的改动.幸运的是,我们在这工作中所需用到的复杂的抽象的T-SQL语句现在可以通过Acess库来替代,像ADO.NET. 不可否认的是,通过ADO.NET来对二进制数据进行操作与对文本数据进行操作是有点不同.在这篇文章中,我们将试验如何运用ADO.NET和ASP.NET2.0中的SqlDataSource数据控件来进行对二进制数据直接在数据库中进行数据存储和修改操作

存储数据在数据库 VS 存储数据在文件系统

  正如上面所说,我们可以采用存储数据在数据库方法,也可以采用存储数据在文件系统的方法.根据我的经验,我发现大多数开发都更愿意将数据存储在文件系统中,这主要归咎于以下几个原因.

•少量的工作—存储二进制数据在数据库中需要所写的代码要比存储在文件系统中多一点,同样在进行数据更新操作时,可以不需要与数据库打交道,直接覆盖原来的文件就可以了!

•文件的URL地址更直接---正如我们所看到的这篇文章中,为了更直接将二进制数据存储器在数据库中,我们需要创建一个新的ASP.NET页面用来返回数据.这个文件只需通过唯一的一个标识来判定在数据库中是哪一个记录被返回.对于一个上传的文件,就拿一个上传的文件的图片来说,其URL可能像这样

http://www.yourserver.com/ShowImage.aspx?ID=4352,而如果这图片是直接存在数据库中的,那它的URL可能是下面这样的

http://www.yourserver.com/UploadedImages/Sam.jpg

•更多的工具支持显示图片—如果你是在使用ASP.NET2.0,则可以直接在GridView或DetailView中使用ImageField控件,通过读取数据中的图片路径来显示图片.而如果图像直接存储在数据库中,ImageField就可以发挥作用了(因为它需要一个页面去检索和返回数据)

•性能—因为二进制数据存储在数据库的大小要远远大于存储在文件系统中,而存储在系统中则使数据库小得多.这样可以降低和减轻对数据库的负载和依赖.

直接将二进制存储在数据库中最主要的优势在于可以让数据”自我兼容”.因为所有的数据都包含在数据库中,备份数据,将一个数据库移至另一个数据库,拷贝数据等相关数据库操作时,就不会像存储在文件系统中有太多的担忧.

  总之,如何选择在于你的使用情况和业务需要.例如,我有一个客户,他的二进制数据需要存储在数据库中,因为他们所用的的报表软件只能在报表中使用来自数据库的二进制数缶.在另一种情况,我的一个同事在它的项目中,二进制数据要通过Web方式和FTP方式读取,那这样就需要直接存取在文件系统中了

创建一个数据库的表用来存储二进制数据

  这篇文章主要用Microsoft Server2005 Experss Edition来阐述一下简单的ASP.NET2.0的图像应用程序,并将数据存储至数据库中.

这个图像应用程序Demo是由一下表所组成,主要是用于存储图片的数据记录.表的字段中包含了图片的类型号(image/jpeg for JPG files, image/gif for GIF 等).这个表的设计如图所示

通过ADO.Net上传一张图片至数据库中

  这个像册程序允许上传GIF,JPGS和PNG格式的文件.当进行上传操作时,数据库中的表会自动增加一条新的记录,相应的字段中都有相对应的数据存在.为了能通过Web浏览器方式上传,我们可以采用ASP.NET2.0中的FileUpload上传控件,我们只要从工具箱中将这控件拖至页面中就可以了.FileUpload控件提供了一种标准的上传文件方式给我们,当通过对话框选择用户硬盘上的文件后,通过点击上传按钮就可以实现上传操作了.

例如:为了增加一张新的图片,我用了一个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服务器端.简单的页面如下所示:

当用户选择了一个图片文件通过(对话框中的插入按钮操作),这个特定的文件夹的数据就会传输至Web服务器.在服务器端,二进制的数据可以通过FileUpload控件进行数据流传输.代码如下:

程序代码:

Protected Sub btnInsert_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnInsert.Click 
 
  'Make sure a file has been successfully uploaded 
 
  If UploadedFile.PostedFile Is Nothing OrElse String.IsNullOrEmpty(UploadedFile.PostedFile.FileName) OrElse UploadedFile.PostedFile.InputStream Is Nothing Then 
 
    ... Show error message ... 
 
    Exit Sub 
 
  End If 
 

  'Make sure we are dealing with a JPG or GIF file 
 
  Dim extension As String = Path.GetExtension(UploadedFile.PostedFile.FileName).ToLower() 
 
  Dim MIMEType As String = Nothing 
 

  Select Case extension 
 
    Case ".gif" 
 
      MIMEType = "image/gif" 
 
    Case ".jpg", ".jpeg", ".jpe" 
 
      MIMEType = "image/jpeg" 
 
    Case ".png" 
 
      MIMEType = "image/png" 
 

    Case Else 
 
      'Invalid file type uploaded 
 
      ... Show error message ... 
 
      Exit Sub 
 
  End Select 
 


  'Connect to the database and insert a new record into 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) 
 

    'Load FileUpload's InputStream into Byte array 
 
    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

  这个触发事件可以用来判断文件是否上传,它可以且来判定所上传的文件类型是否是允许的文件类型

这段代码的关键是天天@imagedate参数的设置.首先,创建了一个imageBytes的数组用来判定数据流的长度.接着,这个byte数组通过read()来将用数据流中的二进制数据进行填充.,整个byte数组的值就是@imageData的值.

运用ASP.NET2.0 DataSource 控件上传一张图片

除了在ASP.NET2.0中运用ADO.NET来实现操作,我们还可以通过运用ASP.NET2.0的DataSource控件直接存储数据,这样甚至可以不用输入法任何ADO.NET的代码.在这文章的最后提供了一个运用SqlDataSource控件和DetailView来实现增加一张新图片至像册的例子(看插入数据).在这个demo中的SqlDataSource控件中就包含了一个InsertCommand和title,MIMETYPE,ImageData三个参数的值.

程序代码:

<asp:SqlDataSource ID="UploadPictureDataSource" runat="server"  
       ConnectionString="..." 
       InsertCommand="INSERT INTO [Pictures] ([Title], [MIMEType], [ImageData]) VALUES (@Title, @MIMEType, @ImageData)"> 
   <InsertParameters> 
     <asp:Parameter Name="Title" Type="String" /> 
     <asp:Parameter Name="MIMEType" Type="String" /> 
     <asp:Parameter Name="ImageData" /> 
   </InsertParameters> 
 </asp:SqlDataSource>

我们注意到imageData 参数并没有设置一个很特殊的数据类型.如果你尝试使用GUI向导设置一个数据源的话,它将很可能设置成一个”Object”的类型.然而,”Object”类型会导致在Sql变量中的类型会出错,这时就不能使用image或varbinary(MAX)类型了,因为在SQL的数据类型中SQL变量的大小不能超过8000字节.如果你使用”Object”类型的话,在保存数据时会出现一个异常错误: Parameter '@ImageData' exceeds the size limit for the sql_variant datatype 我们可以得到异常描述信息:

Implicit conversion from data type sql_variant to varbinary(max) is not allowed. Use the CONVERT function to run this query..

在DetailView控件中包含了两个模板字段—一个是输入图片标题的TextBox对应数据库表中的Title字段,一个是对应数据库表中ImageData的FileUpload上传控件.

程序代码:

Protected Sub UploadPictureUI_ItemInserting(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DetailsViewInsertEventArgs) Handles UploadPictureUI.ItemInserting 
   'Reference the FileUpload control 
   Dim UploadedFile As FileUpload = CType(UploadPictureUI.FindControl("UploadedFile"), FileUpload) 
   'Make sure a file has been successfully uploaded 
   If UploadedFile.PostedFile Is Nothing OrElse String.IsNullOrEmpty(UploadedFile.PostedFile.FileName) OrElse UploadedFile.PostedFile.InputStream Is Nothing Then 
     ... Show error message ... 
     e.Cancel = True 
     Exit Sub 
   End If 
   'Make sure we are dealing with a JPG or GIF file 
   Dim extension As String = Path.GetExtension(UploadedFile.PostedFile.FileName).ToLower() 
   Dim MIMEType As String = Nothing 
 Select Case extension 
     Case ".gif" 
       MIMEType = "image/gif" 
     Case ".jpg", ".jpeg", ".jpe" 
       MIMEType = "image/jpeg" 
     Case ".png" 
       MIMEType = "image/png" 
     Case Else 
       'Invalid file type uploaded 
       ... Show error message ... 
       e.Cancel = True 
       Exit Sub 
   End Select 
   'Specify the values for the MIMEType and ImageData parameters 
   e.Values("MIMEType") = MIMEType 
   'Load FileUpload's InputStream into Byte array 
   Dim imageBytes(UploadedFile.PostedFile.InputStream.Length) As Byte 
   UploadedFile.PostedFile.InputStream.Read(imageBytes, 0, imageBytes.Length) 
   e.Values("ImageData") = imageBytes 
 End Sub

  我们可以从” 运用ASP.NET2.0 DataSource 控件上传一张图片”这部分可以看到,像这样的一个插入Button事件,在DetailView’s的插入事件中有一点区别:当出现错误的时候,我们必须停止这工作流,可以通过设置e.cancel的属性为True实现.

  首先,由于这个FileUpload控件是在一个模板中,那么他必须使用权FindControl(“controlID”)方法实现其本身的作作.当它被找到后,就可能通过其进行同样的上传操作.通过检测后,MIMEType和ImageDate参数需要使用 e.values(“parameterName”)=value的形式同仁.就像在ADO.NET中的例子一样,二进制数据需要先转化成一个数组之后再将其值赋给参数.

显示数据内容

  无论你采用哪种技术存储数据至数据库,为了能编辑和显示二进制数据,我们都需要创建一个新的ASP.NET页面.,我们命名为showPicture.aspx,我们可能通过使用Querstring和从ImageDate字段中返回二进制数据,依靠一个pictureID来实现,图片就可能通过访问/showpicture.aspx?pictureID=pictrueID的方法查看了.因此,我们可能采用一个image控件,设置它的imageUrl属性,这样就可以将一张图片呈现在一个web页面上了.

  在ASPX页面中showPicture.aspx页面并不包含任何HTML标签,在code-behind的加载页中,图片的类型MIMEType字段和图片的数据ImageDate字段都是通过ADO.NET的代码实现存储,下一步则是MIMEType字段和二进制数据内容抬爱 过Response.BinaryWrite(ImageDate)进行赋值.

程序代码:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 
 
  Dim PictureID As Integer = Convert.ToInt32(Request.QueryString("PictureID")) 
 

  'Connect to the database and bring back the image contents & MIME type for the specified picture 
 
  Using myConnection As New SqlConnection(ConfigurationManager.ConnectionStrings("ImageGalleryConnectionString").ConnectionString) 
 

    Const SQL As String = "SELECT [MIMEType], [ImageData] FROM [Pictures] WHERE [PictureID] = @PictureID" 
 
    Dim myCommand As New SqlCommand(SQL, myConnection) 
 
    myCommand.Parameters.AddWithValue("@PictureID", PictureID) 
 

    myConnection.Open() 
 
    Dim myReader As SqlDataReader = myCommand.ExecuteReader 
 

    If myReader.Read Then 
 
      Response.ContentType = myReader("MIMEType").ToString() 
 
      Response.BinaryWrite(myReader("ImageData")) 
 
    End If 
 

    myReader.Close() 
 
    myConnection.Close() 
 
  End Using 
 
End Sub

  当ShowPicture.aspx页面完成后,图片就可能通过Image控件来访问其路径(也可以通过<img src=”showpicute.aspx?productid=”productID”…/>形式实现).如下图  

  下面这个是图像的default.aspx页面,通过一个FormView控件使用了image Web控件来显示图片,它可能允许用户在这个像册中查看更多的图片

  结论:当碰到有二进制数据处理的项目时,开发者必须根据自己的需要来决定采用哪一种存储形式.正如本文所讲到的,需要考虑到很多因素在内.如果你选择了直接存储在数据库中,你就需要在插入/更新/编辑操作数据中多花一点心思,在这篇文章中,我们学到了运用ADO.NET代码和ASP.NET2.0的SqlDataSource控件来实现上传图片并直接存储在数据库中