前面两篇文章,已经讲解了C#对MongoDB的基本操作以及小文件的读写存储,那么对于大型(>=16M)文件呢?具体又该如何操作呢,本文主要以一个简单的小例子,简述C#如何通过GridFS进行MongoDB的大文件的操作。.
什么是GridFS?
在实现GridFS方式前我先讲讲它的原理,为什么可以存大文件。驱动首先会在当前数据库创建两个集合:"fs.files"和"fs.chunks"集合,前者记录了文件名,文件创建时间,文件类型等基本信息;后者分块存储了文件的二进制数据(并支持加密这些二进制数据)。分块的意思是把文件按照指定大小分割,然后存入多个文档中。
"fs.files"怎么知道它对应的文件二进制数据在哪些块呢?
那是因为在"fs.chunks"中有个"files_id"键,它对应"fs.files"的"_id"。
"fs.chunks"还有一个键(int型)"n",它表明这些块的先后顺序。
这两个集合名中的"fs"也是可以通过参数自定义的。
GridFS存储原理
一个文件存储在两个集合中,一个用于存储元数据(文件名称,类型,大小等内容,可便于索引),一个用于存储真实二进制数据(分块存储),如下所示:

GridFS安装
如果需要存储大型文件,则需要安装GridFS插件,如下所示:
项目--右键--管理Nuget程序包--打卡Nuget包管理器--浏览搜索MongoDB.Driver.GridFS--安装。如下所示:

示例截图
首先是文件的查询,如下所示:

文件的新增

核心代码
本示例主要是在MongoDB中进行文件的操作,所以之前的MongoHelper已不再适用,本例新增了文件专用帮助类MongoFileHelper,如下所示:
using MongoDB.Bson;using MongoDB.Driver;using MongoDB.Driver.GridFS;using System;using System.Collections.Generic;using System.IO;using System.Linq;using System.Text;using System.Threading.Tasks;namespace DemoMongo.Common{public class MongoFileHelper{private string connStr = "mongodb://127.0.0.1:27017";//服务器网址private string dbName = "hexdb";//数据库名称private IMongoClient client;//连接客户端private IMongoDatabase db;//连接数据库private string collName;//集合名称public MongoFileHelper(){}public MongoFileHelper(string connStr, string dbName, string collName){this.connStr = connStr;this.dbName = dbName;this.collName = collName;this.Init();}/// <summary>/// 初始化连接客户端/// </summary>private void Init(){if (client == null){client = new MongoClient(this.connStr);}if (db == null){db = client.GetDatabase(this.dbName);}}/// <summary>/// 通过字节方式上传/// </summary>/// <param name="filePath"></param>public void UploadFile(string filePath){IGridFSBucket bucket = new GridFSBucket(db);byte[] source = File.ReadAllBytes(filePath);string fileName = Path.GetFileName(filePath);var options = new GridFSUploadOptions{ChunkSizeBytes = 64512, // 63KBMetadata = new BsonDocument{{ "resolution", "1080P" },{ "copyrighted", true }}};var id = bucket.UploadFromBytes(fileName, source);//返回的ID,表示文件的唯一ID}/// <summary>/// 通过Stream方式上传/// </summary>/// <param name="filePath"></param>public void UploadFile2(string filePath){IGridFSBucket bucket = new GridFSBucket(db);var stream = new FileStream(filePath, FileMode.Open);string fileName = Path.GetFileName(filePath);var options = new GridFSUploadOptions{ChunkSizeBytes = 64512, // 63KBMetadata = new BsonDocument{{ "resolution", "1080P" },{ "copyrighted", true }}};var id = bucket.UploadFromStream(fileName, stream);//返回的ID,表示文件的唯一ID}/// <summary>/// 通过字节写入到流/// </summary>/// <param name="filePath"></param>public void UploadFile3(string filePath){IGridFSBucket bucket = new GridFSBucket(db);byte[] source = File.ReadAllBytes(filePath);string fileName = Path.GetFileName(filePath);var options = new GridFSUploadOptions{ChunkSizeBytes = 64512, // 63KBMetadata = new BsonDocument{{ "resolution", "1080P" },{ "copyrighted", true }}};using (var stream = bucket.OpenUploadStream(fileName, options)){var id = stream.Id;stream.Write(source, 0, source.Length);stream.Close();}}/// <summary>/// 下载文件/// </summary>/// <param name="id"></param>public void DownloadFile(ObjectId id,string filePath){IGridFSBucket bucket = new GridFSBucket(db);byte[] source = bucket.DownloadAsBytes(id);//返回的字节内容//var bytes = await bucket.DownloadAsBytesAsync(id);using (Stream stream = new FileStream(filePath, FileMode.OpenOrCreate)) {stream.Write(source, 0, source.Length);}}public void DownloadFile2(ObjectId id){IGridFSBucket bucket = new GridFSBucket(db);Stream destination = null;bucket.DownloadToStream(id, destination);//返回的字节内容//await bucket.DownloadToStreamAsync(id, destination);}public void DownloadFile3(ObjectId id){IGridFSBucket bucket = new GridFSBucket(db);Stream destination = null;using (var stream = bucket.OpenDownloadStream(id)){// read from stream until end of file is reachedstream.Close();}}public void DownloadFile4(string fileName){IGridFSBucket bucket = new GridFSBucket(db);var bytes = bucket.DownloadAsBytesByName(fileName);// orStream destination = null;bucket.DownloadToStreamByName(fileName, destination);// orusing (var stream = bucket.OpenDownloadStreamByName(fileName)){// read from stream until end of file is reachedstream.Close();}}public List<MongoFile> FindFiles(){IGridFSBucket bucket = new GridFSBucket(db);var filter = Builders<GridFSFileInfo>.Filter.And(//Builders<GridFSFileInfo>.Filter.Eq(x => x.Filename, string.Empty),Builders<GridFSFileInfo>.Filter.Gte(x => x.UploadDateTime, new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc)),Builders<GridFSFileInfo>.Filter.Lt(x => x.UploadDateTime, new DateTime(2022, 2, 1, 0, 0, 0, DateTimeKind.Utc)));var sort = Builders<GridFSFileInfo>.Sort.Descending(x => x.UploadDateTime);var options = new GridFSFindOptions{//Limit = 1,Sort = sort};List<MongoFile> lstFiles = new List<MongoFile>();using (var cursor = bucket.Find(filter, options)){var fileInfos = cursor.ToList();foreach (var fileInfo in fileInfos) {MongoFile f = new MongoFile(){Id=fileInfo.Id,name = fileInfo.Filename,suffix = Path.GetExtension(fileInfo.Filename),size = int.Parse(fileInfo.Length.ToString())};lstFiles.Add(f);}}return lstFiles;}public List<MongoFile> FindFileByName(string fileName){IGridFSBucket bucket = new GridFSBucket(db);var filter = Builders<GridFSFileInfo>.Filter.And(Builders<GridFSFileInfo>.Filter.Eq(x => x.Filename, fileName),Builders<GridFSFileInfo>.Filter.Gte(x => x.UploadDateTime, new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc)),Builders<GridFSFileInfo>.Filter.Lt(x => x.UploadDateTime, new DateTime(2015, 2, 1, 0, 0, 0, DateTimeKind.Utc)));var sort = Builders<GridFSFileInfo>.Sort.Descending(x => x.UploadDateTime);var options = new GridFSFindOptions{//Limit = 1,Sort = sort};List<MongoFile> lstFiles = new List<MongoFile>();using (var cursor = bucket.Find(filter, options)){var fileInfos = cursor.ToList();foreach (var fileInfo in fileInfos){MongoFile f = new MongoFile(){Id = fileInfo.Id,name = fileInfo.Filename,suffix = Path.GetExtension(fileInfo.Filename),size = int.Parse(fileInfo.Length.ToString())};lstFiles.Add(f);}}return lstFiles;}}}
然后操作时,调用帮助类即可,如下所示:
查询调用
private void btnQuery_Click(object sender, EventArgs e){string name = this.txtName.Text.Trim();List<MongoFile> fileInfos = new List<MongoFile>();if (string.IsNullOrEmpty(name)){fileInfos = helper.FindFiles();}else {fileInfos = helper.FindFileByName(name);}this.dgView.AutoGenerateColumns = false;this.bsView.DataSource = fileInfos;this.dgView.DataSource = this.bsView;}
下载调用
private void dgView_CellContentClick(object sender, DataGridViewCellEventArgs e){if (e.ColumnIndex == 3) {//第3个是下载按钮SaveFileDialog sfd = new SaveFileDialog();var file = (MongoFile)(this.dgView.Rows[e.RowIndex].DataBoundItem);sfd.FileName = file.name;sfd.Title = "请保存文件";if (DialogResult.OK == sfd.ShowDialog()){helper.DownloadFile(file.Id,sfd.FileName);MessageBox.Show("保存成功");}}}
保存调用
private void btnSave_Click(object sender, EventArgs e){string filePath = this.txtPath.Text;if (!string.IsNullOrEmpty(filePath)){this.helper.UploadFile(filePath);MessageBox.Show("保存成功");}else {MessageBox.Show("请先选择文件");}}
MongoDB查询
当通过GridFS方式保存文件成功后,会在GridFS Buckets下生成fs对象,且在集合下生成两个集合【fs.files,fs.chunks】,用于存储文件,如下所示:

通过查询fs.files集合,可以查找上传文件的列表,如下所示:

通过查询fs.chunks集合,可以查询文件的内容(二进制数据),如下所示:

注意:如果文件太大,在fs.chunks集合中,进行分片存储,n表示存储的顺序。
以上就是C#操作MongoDB大文件存储的相关内容。