数据的持久化是开发的基础性工作,我们不可避免要将各种的类型文件持久化,关于文件(或是大对象)的存储,我在我的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 文件下载
到此大功告成,当然您也可以在这基础之上把功能再加大,比如实现编辑,目录方式等,今天是周未,登峰祝您周未愉快!