數據的持久化是開發的基礎性工作,我們不可避免要將各種的類型文件持久化,關于文件(或是大對象)的存儲,我在我的blog http://www.cnblogs.com/supercode/articles/156744.html談過
今天我們從設計的角度來實現這功能,從本文中您將了解道以下內容
l         sql server中的數據類型
l         數據表,存儲過程的設計
l         邏輯層實現各種類型文件的自動轉化
l         datagrid中的自定義文件列表顯示方式,以及從服務端將文件發送客戶端時的一些技巧
 1. sql server中的數據類型
 
unicode 字符串
nchar
固定長度的 unicode 數據,最大長度為 4,000 個字符。 
nvarchar
可變長度 unicode 數據,其最大長度為 4,000 字符。sysname 是系統提供用戶定義的數據類型,在功能上等同于 nvarchar(128),用于引用數據庫對象名。
ntext
可變長度 unicode 數據,其最大長度為 2^30 - 1 (1,073,741,823) 個字符。
二進制字符串
binary
固定長度的二進制數據,其最大長度為 8,000 個字節。
varbinary
可變長度的二進制數據,其最大長度為 8,000 個字節。
image
可變長度的二進制數據,其最大長度為 2^31 - 1 (2,147,483,647) 個字節。 
 
要想更加詳細的數據類型請查閱sql server自帶的幫助文件,之所以把這幾個羅列出來是因為,網上很多朋友經常問到底用binary還是用image作為存儲的數據類型,很顯然,應該用image,因為很少有文件小于8k的,除非是網絡圖像(jpeg,gif,png)
 
2. 數據表,存儲過程的設計
 (1)創建表
下面是創建表的sql
if exists (select * from dbo.sysobjects where id = object_id(n[dbo].[filelib]) and objectproperty(id, nisusertable) = 1)
drop table [dbo].[filelib]
go
 
create table [dbo].[filelib] (
       [id] [int] identity (1, 1) not null ,
       [fname] [nvarchar] (255) collate chinese_prc_ci_as null ,
       [filetype] [nvarchar] (50) collate chinese_prc_ci_as null ,
       [filecontent] [image] null ,
       [filesize] [float] null ,
       [fileuploader] [nvarchar] (50) collate chinese_prc_ci_as null ,
       [uploaddate] [datetime] null ,
       [icon] [nvarchar] (50) collate chinese_prc_ci_as null 
) on [primary] textimage_on [primary]
go
 
關系圖如下
           
     圖1存儲文件的表
(2)創建存儲過程
1.         寫入數據庫的存儲過程
/**//************************************************************
*  purpose: test for upload file to sql server                                                       *
*  author:    登峰                                                                                                     *
*  blog:  http://www.cnblogs.com/supercode                                                             *
*  date:       2005-6-12                                                                                             *
*****************************************************************/
 
create proc  setfiletodb
@_filename  as  nvarchar(255) = null,
@_filetype  as nvarchar(50) = null, 
@_filecontent as image = null, 
@_filesize as int =null,
@_fileuploader as nvarchar(50)=null,
@_uploaddate  as datetime =null,
@_icon as nvarchar(50)=null
as
 
--聲明sql變量
   declare @createtabsql as nvarchar(100);
 
--聲明錯誤處理變量
   declare @currenterror int
 
 
 
     
  --  事務開始
    begin transaction
   --插入表
   insert  into filelib(fname,filetype,filecontent,filesize,fileuploader,uploaddate,icon)
                               values( @_filename,@_filetype,@_filecontent,@_filesize,@_fileuploader,@_uploaddate,@_icon)
 
 
 
   select @currenterror = @@error
    if @currenterror != 0
        begin
             goto error_handler
        end
  
-- 事務結束
    commit transaction
 
 -- 成功的話返回0
    return 0
  error_handler:
        rollback transaction
          return @currenterror
go 
3. 邏輯層實現各種類型文件的自動轉化
   引用這層的目的就是簡要說明一下層次的問題,本來數據層也獨立出來,但這文章的目的不在于此,所以附帶而過,而且這邏輯層也非常簡單,為了方便起見,把相關的類和操作都放在一起文件里
(3.1)定義文件實體類
class fileentity
    {
          private int _id;
          private string _filename;
          private string _filetype;
          private byte[] _filecontent;
          private int _filesize;
          private string _fileuploader;
          private datetime _uploaddate;
          private string _icon;
        
        屬性#region 屬性
 
        public int id
        {
            set{_id=value;}
            get{ return id;}
        }
 
        public string filename
        {
            set{_filename=value;}
            get{ return _filename;}
        }
 
        public string filetype
        {
            set{_filetype=value;}
            get{ return _filetype;}
        }
 
        public byte[] filecontent
        {
            set{_filecontent=value;}
            get{ return _filecontent;}
        }
 
        public int filesize
        {
            set{_filesize=value;}
            get{ return _filesize;}
        }
 
        public string fileuploader
        {
            set{_fileuploader=value;}
            get{ return _fileuploader;}
        }
 
        public datetime uploaddate
        {
            set{_uploaddate=value;}
            get{ return _uploaddate;}
        }
 
        public string icon
        {
            set{_icon=value;}
            get{ return _icon;}
        }
 
        #endregion
 
    } 
(3.2)擴展名和圖標的處理
   要想在列表里實現哪種類型的文件對應哪種圖標,這需求關關聯,數據庫中icon就是來保存文件的擴展名的,看下面兩個處理方法
   /**//// <summary>
        /// 從本地的全名路徑(含文件名)中獲取文件名
        /// </summary>
        /// <param name="path">全名路徑(含文件名)</param>
        /// <returns>文件名</returns>
        private string getfilename(string path)
        {
            int index=path.lastindexof("//");
            return path.substring(index+1);
 
        }
        /**//// <summary>
        /// 從本地的全名路徑(含文件名)中獲取文件的擴展名
        /// </summary>
        /// <param name="path">全名路徑(含文件名)</param>
        /// <returns>文件的擴展名</returns>
        private string  getextename(string path)
        {
            int index=path.lastindexof(".");
            return path.substring(index+1);
        }
4. 頁面的實現
 4.1 頁面hmtl的描述
確信你設定了form的enctype屬性為multipart/form-data。顯示文件列表的關鍵是datagird,先看看他的描述
   <asp:datagrid id="datagrid1" autogeneratecolumns="false" style="z-index: 101; left: 120px; position: absolute; top: 88px"
                            runat="server" width="664px" height="152px" bordercolor="#cc9966" borderstyle="none" borderwidth="1px"
                            backcolor="white" cellpadding="4">
                            <footerstyle forecolor="#330099" backcolor="#ffffcc"></footerstyle>
                            <selecteditemstyle font-bold="true" forecolor="#663399" backcolor="#ffcc66"></selecteditemstyle>
                            <itemstyle forecolor="#330099" backcolor="white"></itemstyle>
                            <headerstyle font-bold="true" forecolor="#ffffcc" backcolor="#990000"></headerstyle>
                            <pagerstyle horizontalalign="center" forecolor="#330099" backcolor="#ffffcc"></pagerstyle>
                            <columns>
                                   <asp:templatecolumn headertext="圖標">
                                          <itemtemplate>
                                                 <img src=http://www.pushad.com/info/images/<%# (databinder.eval(container.dataitem, "icon").tostring()) %>.gif />
                                          </itemtemplate>
                                   </asp:templatecolumn>
                                   <asp:templatecolumn headertext="文件名">
                                          <itemtemplate>
                                          <a href=webform1.aspx?fid=<%# databinder.eval(container.dataitem,"fid")%> > <%# databinder.eval(container.dataitem, "fname") %> </a>
                                                  
                                          </itemtemplate>
                                   </asp:templatecolumn>
                                   <asp:templatecolumn headertext="內部類型">
                                          <itemtemplate>
                                                 <asp:label id="filename" runat="server" text=<%# databinder.eval(container.dataitem, "filetype") %> />
                                          </itemtemplate>
                                   </asp:templatecolumn>
                                   <asp:templatecolumn headertext="文件大小">
                                          <itemtemplate>
                                                 <asp:label id="filesize" runat="server" text=<%# databinder.eval(container.dataitem, "filesize","{0:n}") %> />
                                          </itemtemplate>
                                   </asp:templatecolumn>
                                   <asp:templatecolumn headertext="上傳者">
                                          <itemtemplate>
                                                 <asp:label id="fileuploader" runat="server" text=<%# databinder.eval(container.dataitem, "fileuploader") %> />
                                          </itemtemplate>
                                   </asp:templatecolumn>
                                   <asp:templatecolumn headertext="上傳日期">
                                          <itemtemplate>
                                                 <asp:label id="uploaddate" text=<%# databinder.eval(container.dataitem, "uploaddate","{0:yyyy-mm-dd}") %> runat="server"/>
                                          </itemtemplate>
                                   </asp:templatecolumn>
                            </columns>
                     </asp:datagrid> 
4.2 把db中的文件顯示在datagrid上,因為僅僅是顯示,所以filecontent字段沒有必要讀出來,而且在這里我們自定義一個datatable來綁定到datagrid中,請看下面的代碼
 1 /**//// <summary>
 2        /// 從數據庫中讀取文件信息顯示在datagrid上
 3        /// </summary>
 4        private void bindgrid()
 5        {
 6            
 7            string selectcommand="select id,fname,filetype,filesize,fileuploader,uploaddate,icon from filelib";
 8 
 9            sqlconnection myconnection=null;
10            try
11            {
12               myconnection = new sqlconnection(connectionstring);
13                    myconnection.open();
14                            
15                sqlcommand  sqlcmd=new sqlcommand(selectcommand,myconnection);
16                
17                
18                sqldatareader ddr=sqlcmd.executereader();
19                  
20                datatable dt = new datatable();
21                datarow dr;             
22                dt.columns.add(new datacolumn("filetype", typeof(string)));//0
23                dt.columns.add(new datacolumn("fname", typeof(string)));//1
24                dt.columns.add(new datacolumn("filesize", typeof(string)));//2
25                dt.columns.add(new datacolumn("fileuploader", typeof(string)));//3
26                dt.columns.add(new datacolumn("uploaddate", typeof(string)));//4
27                dt.columns.add(new datacolumn("icon", typeof(string)));//5
28                 dt.columns.add(new datacolumn("fid", typeof(string)));//6
29                                  
30              
31                while(ddr.read())
32                {                   
33                    dr=dt.newrow();//新一行             
34                    dr[0]=ddr["filetype"].tostring(); 
35                    dr[1]=ddr["fname"].tostring(); 
36                    dr[2]=ddr["filesize"].tostring(); 
37                    dr[3]=ddr["fileuploader"].tostring(); 
38                    dr[4]=ddr["uploaddate"].tostring(); 
39                    dr[5]=ddr["icon"].tostring(); 
40                    dr[6]=ddr["id"].tostring(); 
41                    dt.rows.add(dr);
42 
43                }
44                //綁字到datagrid
45                datagrid1.datasource =  new dataview(dt);
46                datagrid1.databind();
47            } 
48            catch (system.exception ex)
49            {
50                response.write(ex.message+ex.stacktrace);
51            }
52            finally
53            {
54                myconnection.close();
55            }
56        } 
4.3 上傳文件至sql server 數據庫
     iis對上傳的大小是很限定,當然這在web.config中配置,具體的這里不詳述,再查閱相關的資料,我們先把頁面級的字段放在文件實體類中,再將實體類傳到邏輯層來處理,三層的原理也是如此,下面是初始化提交代碼
void initentity()
        {
            //讀取相關值
            fileentity fe=new fileentity();         
            stream datastream = file1.postedfile.inputstream; //文件流
            fe.filesize=file1.postedfile.contentlength; //文件長度
            
            byte[] bdata = new byte[fe.filesize];         
            int n = datastream.read(bdata,0,fe.filesize); //全部讀取緩沖,n代表實際讀取字節數
            
            fe.filecontent =bdata;
            fe.filename=getfilename(file1.postedfile.filename);//全稱
            fe.fileuploader=this.txtuploader.text;          
            fe.filetype=file1.postedfile.contenttype;
            fe.uploaddate=datetime.now; 
            fe.icon=getextename(file1.postedfile.filename);
            //開始寫入數據庫
           uploadfiletodb(fe);
        }
下面的代碼是寫入數據庫的代碼
 /**//// <summary>
        /// 上傳文件至數據庫
        /// </summary>
        private void uploadfiletodb(fileentity fe)
        {
            sqlconnection myconnection =null;
            try
            {           
                myconnection = new sqlconnection(connectionstring); 
 
                sqlcommand command = new sqlcommand("setfiletodb", myconnection);
                command.commandtype = commandtype.storedprocedure;
        
                command.parameters.add("@_filename", fe.filename);
                command.parameters.add("@_filetype", fe.filetype);
                command.parameters.add("@_filecontent",fe.filecontent);
                command.parameters.add("@_filesize", fe.filesize);
                command.parameters.add("@_fileuploader", fe.fileuploader);
                command.parameters.add("@_uploaddate", fe.uploaddate);      
                command.parameters.add("@_icon", fe.icon);  
    
                myconnection.open();
                int result=command.executenonquery();
 
                bindgrid();
            }
            catch(exception ex)
            {
                response.write(ex.message+ex.stacktrace);
            }
            finally
            {
                myconnection.close();
            }             
        } 
4.4 下載文件時的處理過程
當然這是也是最關鍵的,不然沒意義了,當然這會涉及到小問題,比如,下載時的中文亂碼問題,對jpeg或word文件是下載還是直接在ie中打開等問題,這些問題我都已在下面的代碼中解決
/**//// <summary>
        ///  將文件發給客戶端,fid是本面load時從request讀取的
        /// </summary>
        /// <param name="fid">文件編號</param>
        void downloadfile(int fid)
        {
            sqlconnection myconnection=null;    
        
            string strdownloadsql="select filetype,fname,filecontent from filelib where id="+fid;
 
            try
            {
                myconnection    = new sqlconnection(connectionstring);
                myconnection.open();
                            
                sqlcommand  sqlcmd=new sqlcommand(strdownloadsql,myconnection);             
                sqldatareader ddr=sqlcmd.executereader();
                if(ddr.read())
                {
                     
                        response.contenttype = ddr["filetype"].tostring();  
 
                        response.addheader("content-disposition", "attachment;filename="+httputility.urlencode(ddr["fname"].tostring(),system.text.encoding.utf8 ));
 
                        response.binarywrite( (byte[]) ddr["filecontent"] );  
                         response.end();
 
                }
                
            }
 
            catch(exception ex)
            {
                response.write(ex.message+ex.stacktrace);
            }
            finally
            {
                myconnection.close();
            } 
        } 
好,文章到這也即將結束,最后我們來看看最終的demo效果
 
   
                                圖2 文件上傳后
下面看看下載時的效果
    
                                                                      圖3 文件下載
到此大功告成,當然您也可以在這基礎之上把功能再加大,比如實現編輯,目錄方式等,今天是周未,登峰祝您周未愉快!