登峰之道---简约而不简单
分享阳光,分享快乐
posts - 45,  comments - 94,  trackbacks - 1
各种类型文件在SQL Server中存储的解决方案
 
数据的持久化是开发的基础性工作,我们不可避免要将各种的类型文件持久化,关于文件(或是大对象)的存储,我在我的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, N'IsUserTable'= 1)
drop table [dbo].[FileLib]
GO
 
CREATE TABLE [dbo].[FileLib] (
       
[ID] [int] IDENTITY (11NOT 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;}
            
getreturn ID;}
        }

 
        
public string FileName
        
{
            
set{_FileName=value;}
            
getreturn _FileName;}
        }

 
        
public string FileType
        
{
            
set{_FileType=value;}
            
getreturn _FileType;}
        }

 
        
public byte[] FileContent
        
{
            
set{_FileContent=value;}
            
getreturn _FileContent;}
        }

 
        
public int FileSize
        
{
            
set{_FileSize=value;}
            
getreturn _FileSize;}
        }

 
        
public string FileUploader
        
{
            
set{_FileUploader=value;}
            
getreturn _FileUploader;}
        }

 
        
public DateTime UploadDate
        
{
            
set{_UploadDate=value;}
            
getreturn _UploadDate;}
        }

 
        
public string Icon
        
{
            
set{_Icon=value;}
            
getreturn _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='images/<%# (DataBinder.Eval(Container.DataItem, "Icon").ToString()) %>.gif' />
                                          
</ItemTemplate>
                                   
</asp:TemplateColumn>
                                   
<asp:TemplateColumn HeaderText="文件名">
                                          
<ItemTemplate>
                                          
<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 文件下载
 
到此大功告成,当然您也可以在这基础之上把功能再加大,比如实现编辑,目录方式等,今天是周未,登峰祝您周未愉快!
posted on 2005-06-13 12:15 登峰 阅读(4312) 评论(16)  编辑 收藏 网摘 所属分类: 数据库&XML.Net专区

FeedBack:
2005-06-13 14:03 | Pumpkin
感觉文件还是放在目录里面管理比较好,sql里面只保存连接。
  回复  引用    
2005-06-13 14:47 | chixiaojin      
上传文件后....怎么调用杀毒软件进行杀毒....?
  回复  引用  查看    
2005-06-13 17:18 | barton131420
Pumpkin的意见会有很好的数据库访问性能。但是会因为数据库无法保证对目录中文件的依赖,所以需要在显示时判断文件是否存在,这样就多少显得有些别扭。
  回复  引用    
2005-06-13 17:59 | 奋斗中的灵感之源      
非常反感不文件内容放在数据库中
  回复  引用  查看    
#5楼[楼主]
2005-06-13 18:29 | 登峰      
To Pumpkin :
文件系统的性能确实比DB里好,就是维护麻烦些了,现在准备写个目录文件监视器,以保持与数据库的同步更新


To chixiaojin :
这个简单,我知道您用的是什么杀毒软件,每种杀毒都有选定文件在弹出菜单直接杀毒的,这样你可以在注册表里查询一下,参数调用的问题
比如我用的是MAcfee,其调用如下(比如扫描gdiplus.dll)

D:\Program Files\Network Associates\VirusScan>scan32 c:\gdiplus.dll
这样你在服务端就可以直接把刚才上传来的文件进行扫描了

  回复  引用  查看    
2005-06-14 08:07 | LOVE风      
放在数据库中,"可能"在迁移时,有些方便
一般都会放在目录中,这样管理起来也方便些,
就比如现在做了一个政府的报表管理系统,就要接送企业的报表,文档上传,由于量很多,基本每周上万,所以要进行分类,把不同类型的传到不同的目录中,而且按日期划分,这样政府里面直接到自己服务器上把需要的报表拷下来,不用再能过网页一个个下载了,

  回复  引用  查看    
2005-06-14 19:18 | chixiaojin      
class FileEntity这个类不是多余的吗.....
用数组不是更好........你还是有其它的意思..........

  回复  引用  查看    
2005-06-14 19:24 | chixiaojin      
每个表定义单独的业务实体..............那不是要花很多时间去做吗.........我们就用一个arrlist数组来替代class FileEntity类那不就是更好.
===========
多多指教.....能不能说说你的用意.....哈哈..........

  回复  引用  查看    
#9楼[楼主]
2005-06-15 19:13 | 登峰      
To: chixiaojin
增加一个实体类,应该符合面向对象的要求,条理清晰,就好比控制参数一个,你传进1或是2别人不知道是什么意思,但你传进去Color.Red或是Color.Blue这样的优势就体现出来

  回复  引用  查看    
2005-07-01 02:04 | hots
有VB版的吗?
  回复  引用    
#11楼[楼主]
2005-07-18 22:17 | 登峰      
不好意思,没有vb版,不过你可以找个代码转换工具
  回复  引用  查看    
2006-01-09 18:20 | weichenyoumeng[未注册用户]
第一列的图标怎么显示不出来呢
???
<img src='images/<%# (DataBinder.Eval(Container.DataItem, "Icon").ToString()) %>.gif' />

  回复  引用    
2007-09-04 16:04 | Robot·H      
对于上百M的文件,你的程序就over了。
探讨一下
MSN:glory_yimart@hotmail.com

  回复  引用  查看    
#14楼[楼主]
2007-09-16 19:38 | 登峰      
一般几十M的文件,存于文件系统中.
这样的文件,如果普通http传的话,会超时
用ajax带进度显示,这样更有体验

  回复  引用  查看    
2008-04-12 20:12 | answer11[未注册用户]
用你的例子我怎么实现不了下载功能,是哪里出现问题了.

  回复  引用    
2008-04-14 17:27 | 软件[未注册用户]
answer11 是什么错误,能具体指出来吗
  回复  引用    



发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 173529




相关文章:

相关链接:

分享是一种快乐
交流就是分享

<2005年6月>
2930311234
567891011
12131415161718
19202122232425
262728293012
3456789

与我联系

搜索

 

常用链接

留言簿

我参加的小组

随笔分类(36)

随笔档案(45)

文章分类(38)

文章档案(38)

相册

读书

  • 最近在读的书

网友

优秀的.Net网站链接

最新评论

阅读排行榜

评论排行榜