将文件上传/下载到/从 SQL Server 数据库
此示例项目演示了如何将文件保存到 SQL Server 数据库,以及如何从中检索文件。
引言
很久以前,我曾致力于寻找将文件(*.doc*、*.txt*、*.pdf*)与 SQL Server 2005/2008 相关联的正确语法和方法。虽然我发现了一些关于如何将文件上传到 SQL Server 的二进制字段类型的良好示例,但我发现关于如何检索文件和逆向操作的良好示例却很少。为了解决其他从事类似项目的人遇到的这个问题,我创建了一个使用 C# 和 Visual Studio 2010 的小型 WinForms 项目,以演示不仅如何将文件保存到二进制字段类型,还如何检索它们。
背景
SQL Server 允许您创建一个称为“varbinary
”类型的字段。这种字段类型(当与“(max)”长度一起使用时)非常适合将文件直接存储在 SQL Server 数据库中。虽然您也可以只存储文件路径的字符串值,该值将作为“链接”指向文件系统上的实际文件,但您需要一个单独的应用程序文件存储区域,以及一种前端连接和操作文件的方式。相反,像 varbinary(max)
这样的 SQL Server 字段类型允许您像在表中存储其他任何类型的数据一样简单地存储文件——就如同在元数据旁边存储在伴随行中以构成有意义的记录一样。
使用代码
本示例项目提供了将任何类型的文件上传/下载到/从数据库的完整示例。要充分利用此代码,您需要创建一个像文章中示例一样的数据库,或者挂载提供的示例数据库,并在主窗体代码中更新连接字符串。此项目还假定您拥有 Visual Studio 2010 Express (C#) 或更高版本。
高级代码内容
此项目由一个窗体和一个类组成。
- frmMain.cs - 这是提供所需功能的类/窗体。在生产项目中,这很可能会被分解为一个或多个类。主窗体具有一个简单的数据网格视图布局,用于显示已上传的文件。
- db2.mdf - 一个 SQL 数据库 - 您必须挂载此示例数据库(或构建您自己的类似数据库——其中只有一个表)。请确保更新您的连接字符串。
- 不必担心应用程序配置文件——它在这里不使用——只需根据需要修改 frmMain.cs 区域中的此行(我猜是第 16 行)=>
string strSqlConn = @"Data Source=localhost\sqlexpress;Initial Catalog=db2;Integrated Security=True";
。 - 唯一的表
tblAttachments
的结构如下:
id -> (int, primary key, identity)
fileName -> (nvarchar(250))
fileSize -> (int)
attachment -> (varbinary(max))
这三个查询
主要使用三个查询,它们被定义为字符串变量。您将在项目中到处看到它们。
string strQuery_AllAttachments =
"select [id], [fileName], [fileSize] from [tblAttachments] order by [fileName]";
string strQuery_GetAttachmentById =
"select * from [tblAttachments] where [id] = @attachId";
string strQuery_AllAttachments_AllFields = "select * from [tblAttachments]";
数据网格
这里是主窗体加载事件,它设置连接字符串并将所有附件填充到窗体中的网格视图中。
private void frmMain_Load(object sender, EventArgs e)
{
objConn.ConnectionString = strSqlConn; //set connection params
FillDataGrid(gridViewMain, strQuery_AllAttachments);
}
此函数用于创建一个具有表架构(自动填充)和传入查询字符串的 SQL 数据适配器。然后使用数据适配器填充数据表。最后,使用传入的网格视图引用将数据表设置为网格视图数据源。此函数可用于每次数据网格刷新。
private void FillDataGrid(DataGridView objGrid, string strQuery)
{
DataTable tbl1 = new DataTable();
SqlDataAdapter adapter1 = new SqlDataAdapter();
SqlCommand cmd1 = new SqlCommand();
cmd1.Connection = objConn; // use connection object
cmd1.CommandText = strQuery; // set query to use
adapter1.MissingSchemaAction = MissingSchemaAction.AddWithKey; //grab schema
adapter1.SelectCommand = cmd1; //
adapter1.Fill(tbl1); // fill the data table as specified
objGrid.DataSource = tbl1; // set the grid to display data
}
添加/上传文件
这是开始添加文件的代码——它会创建一个打开文件对话框,允许您选择要上传的文件。如果您选择了一个有效文件,就会开始上传到数据库字段。一旦 CreateAttachment
方法运行,网格视图就会使用 FillDataGrid
进行刷新。
private void btnAddFile_Click(object sender, EventArgs e)
{
if (ofdMain.ShowDialog() != DialogResult.Cancel)
{
CreateAttachment(ofdMain.FileName); //upload the attachment
}
FillDataGrid(gridViewMain, strQuery_AllAttachments); // refresh grid
}
在此方法中完成创建附件。此方法执行的操作与 FillDataGrid
类似。会创建一个内存中的数据表和适配器,并使用查询字符串进行填充。然后,我们使用传入的文件(在打开文件对话框使用时选择的文件)创建一个 FileStream
对象。这将允许程序以只读模式打开目标文件。使用字节数组存储读取的文件,以便可以将其“推入”varbinary(max)
列。intLength
用于创建一个大小正确、与文件大小匹配的字节数组。接下来,向内存中的数据表添加一个新行,并创建一个新记录。最后,数据适配器将数据表更改提交回“实际”数据库。
private void CreateAttachment(string strFile)
{
SqlDataAdapter objAdapter =
new SqlDataAdapter(strQuery_AllAttachments_AllFields, objConn);
objAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
SqlCommandBuilder objCmdBuilder = new SqlCommandBuilder(objAdapter);
DataTable objTable = new DataTable();
FileStream objFileStream =
new FileStream(strFile, FileMode.Open, FileAccess.Read);
int intLength = Convert.ToInt32(objFileStream.Length);
byte[] objData;
objData = new byte[intLength];
DataRow objRow;
string[] strPath = strFile.Split(Convert.ToChar(@"\"));
objAdapter.Fill(objTable);
objFileStream.Read(objData, 0, intLength);
objFileStream.Close();
objRow = objTable.NewRow();
//clip the full path - we just want last part!
objRow["fileName"] = strPath[strPath.Length - 1];
objRow["fileSize"] = intLength / 1024; // KB instead of bytes
objRow["attachment"] = objData; //our file
objTable.Rows.Add(objRow); //add our new record
objAdapter.Update(objTable);
}
下载文件
此代码开始从 SQL Server 表检索文件的过程。我们调用 SaveAttachment
,这将在下一节中进行解释。另请注意,我调用了 FillDataGrid
,这在技术上不是必需的,但以防万一我稍后添加了删除函数(或其他类似功能)(但我最终没有添加——这是给您留的作业!)。
private void btnDownloadFile_Click(object sender, EventArgs e)
{
SaveAttachment(sfdMain, gridViewMain);
FillDataGrid(gridViewMain, strQuery_AllAttachments); // refresh grid
}
这是此项目的最后一个主要方法。首先,我们获取调用方法时在网格视图中选择的行(记住,我们通过引用传递了网格视图对象)。如果代表 id
字段的单元格不为空,则继续——这可以避免我们在未选择任何行时单击下载按钮时出现尴尬的错误。同样,我们创建一个内存中的数据适配器和数据表,自动填充架构。请注意,这次查询是参数化的——@attachid
。此查询与其他查询不同,因为它只返回一行。然后,我们创建一个字节数组,然后将 objRow["attachment"]
强制转换为字节数组,以便我们可以正确地从 attachment
字段中获取文件。最后,我们显示“另存为”对话框,让用户选择传入文件的文件名和位置;FileStream
再次发挥作用,允许我们创建并写入选定的文件和路径。如果一切顺利,文件应该会出现并且可以使用!
private void SaveAttachment(SaveFileDialog objSfd, DataGridView objGrid)
{
string strId = objGrid.SelectedRows[0].Cells["id"].Value.ToString();
if (!string.IsNullOrEmpty(strId))
{
SqlCommand sqlCmd = new SqlCommand(strQuery_GetAttachmentById, objConn);
sqlCmd.Parameters.AddWithValue("@attachId", strId);
SqlDataAdapter objAdapter = new SqlDataAdapter(sqlCmd);
DataTable objTable = new DataTable();
DataRow objRow;
objAdapter.MissingSchemaAction = MissingSchemaAction.AddWithKey;
SqlCommandBuilder sqlCmdBuilder = new SqlCommandBuilder(objAdapter);
objAdapter.Fill(objTable);
objRow = objTable.Rows[0];
byte[] objData;
objData = (byte[])objRow["attachment"];
if (objSfd.ShowDialog() != DialogResult.Cancel)
{
string strFileToSave = objSfd.FileName;
FileStream objFileStream =
new FileStream(strFileToSave, FileMode.Create, FileAccess.Write);
objFileStream.Write(objData, 0, objData.Length);
objFileStream.Close();
}
}
}
关注点
我很高兴能为您制作这个示例,以便您学习和使用。如果您有任何意见或疑问,请随时通过在此帖子下留言与我联系。
历史
- 1.0 - 2011 年 7 月 8 日 - 首次发布。