前面两篇文章,已经讲解了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存储原理

一个文件存储在两个集合中,一个用于存储元数据(文件名称,类型,大小等内容,可便于索引),一个用于存储真实二进制数据(分块存储),如下所示:

C# 玩转MongoDB(三)_mongodb

 

 GridFS安装

如果需要存储大型文件,则需要安装GridFS插件,如下所示:

项目--右键--管理Nuget程序包--打卡Nuget包管理器--浏览搜索MongoDB.Driver.GridFS--安装。如下所示:

C# 玩转MongoDB(三)_分块_02

示例截图

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

C# 玩转MongoDB(三)_MongoDB_03

文件的新增

C# 玩转MongoDB(三)_C#_04

 

 核心代码

本示例主要是在MongoDB中进行文件的操作,所以之前的MongoHelper已不再适用,本例新增了文件专用帮助类MongoFileHelper,如下所示:



1 using MongoDB.Bson;
2 using MongoDB.Driver;
3 using MongoDB.Driver.GridFS;
4 using System;
5 using System.Collections.Generic;
6 using System.IO;
7 using System.Linq;
8 using System.Text;
9 using System.Threading.Tasks;
10
11 namespace DemoMongo.Common
12 {
13
14 public class MongoFileHelper
15 {
16
17 private string connStr = "mongodb://127.0.0.1:27017";//服务器网址
18
19 private string dbName = "hexdb";//数据库名称
20
21 private IMongoClient client;//连接客户端
22
23 private IMongoDatabase db;//连接数据库
24
25 private string collName;//集合名称
26
27 public MongoFileHelper()
28 {
29
30 }
31
32 public MongoFileHelper(string connStr, string dbName, string collName)
33 {
34 this.connStr = connStr;
35 this.dbName = dbName;
36 this.collName = collName;
37 this.Init();
38 }
39
40 /// <summary>
41 /// 初始化连接客户端
42 /// </summary>
43 private void Init()
44 {
45 if (client == null)
46 {
47 client = new MongoClient(this.connStr);
48 }
49 if (db == null)
50 {
51 db = client.GetDatabase(this.dbName);
52 }
53 }
54
55 /// <summary>
56 /// 通过字节方式上传
57 /// </summary>
58 /// <param name="filePath"></param>
59 public void UploadFile(string filePath)
60 {
61 IGridFSBucket bucket = new GridFSBucket(db);
62 byte[] source = File.ReadAllBytes(filePath);
63 string fileName = Path.GetFileName(filePath);
64 var options = new GridFSUploadOptions
65 {
66 ChunkSizeBytes = 64512, // 63KB
67 Metadata = new BsonDocument
68 {
69 { "resolution", "1080P" },
70 { "copyrighted", true }
71 }
72 };
73 var id = bucket.UploadFromBytes(fileName, source);
74 //返回的ID,表示文件的唯一ID
75
76
77 }
78
79 /// <summary>
80 /// 通过Stream方式上传
81 /// </summary>
82 /// <param name="filePath"></param>
83 public void UploadFile2(string filePath)
84 {
85 IGridFSBucket bucket = new GridFSBucket(db);
86 var stream = new FileStream(filePath, FileMode.Open);
87
88 string fileName = Path.GetFileName(filePath);
89 var options = new GridFSUploadOptions
90 {
91 ChunkSizeBytes = 64512, // 63KB
92 Metadata = new BsonDocument
93 {
94 { "resolution", "1080P" },
95 { "copyrighted", true }
96 }
97 };
98 var id = bucket.UploadFromStream(fileName, stream);
99 //返回的ID,表示文件的唯一ID
100
101
102 }
103
104 /// <summary>
105 /// 通过字节写入到流
106 /// </summary>
107 /// <param name="filePath"></param>
108 public void UploadFile3(string filePath)
109 {
110 IGridFSBucket bucket = new GridFSBucket(db);
111 byte[] source = File.ReadAllBytes(filePath);
112 string fileName = Path.GetFileName(filePath);
113 var options = new GridFSUploadOptions
114 {
115 ChunkSizeBytes = 64512, // 63KB
116 Metadata = new BsonDocument
117 {
118 { "resolution", "1080P" },
119 { "copyrighted", true }
120 }
121 };
122 using (var stream = bucket.OpenUploadStream(fileName, options))
123 {
124 var id = stream.Id;
125 stream.Write(source, 0, source.Length);
126 stream.Close();
127 }
128 }
129
130 /// <summary>
131 /// 下载文件
132 /// </summary>
133 /// <param name="id"></param>
134 public void DownloadFile(ObjectId id,string filePath)
135 {
136 IGridFSBucket bucket = new GridFSBucket(db);
137 byte[] source = bucket.DownloadAsBytes(id);
138 //返回的字节内容
139 //var bytes = await bucket.DownloadAsBytesAsync(id);
140 using (Stream stream = new FileStream(filePath, FileMode.OpenOrCreate)) {
141 stream.Write(source, 0, source.Length);
142 }
143 }
144
145 public void DownloadFile2(ObjectId id)
146 {
147 IGridFSBucket bucket = new GridFSBucket(db);
148 Stream destination = null;
149 bucket.DownloadToStream(id, destination);
150 //返回的字节内容
151 //await bucket.DownloadToStreamAsync(id, destination);
152
153 }
154
155 public void DownloadFile3(ObjectId id)
156 {
157 IGridFSBucket bucket = new GridFSBucket(db);
158 Stream destination = null;
159 using (var stream = bucket.OpenDownloadStream(id))
160 {
161 // read from stream until end of file is reached
162 stream.Close();
163 }
164 }
165
166 public void DownloadFile4(string fileName)
167 {
168 IGridFSBucket bucket = new GridFSBucket(db);
169 var bytes = bucket.DownloadAsBytesByName(fileName);
170
171 // or
172
173 Stream destination = null;
174 bucket.DownloadToStreamByName(fileName, destination);
175
176 // or
177
178 using (var stream = bucket.OpenDownloadStreamByName(fileName))
179 {
180 // read from stream until end of file is reached
181 stream.Close();
182 }
183 }
184
185 public List<MongoFile> FindFiles()
186 {
187 IGridFSBucket bucket = new GridFSBucket(db);
188 var filter = Builders<GridFSFileInfo>.Filter.And(
189 //Builders<GridFSFileInfo>.Filter.Eq(x => x.Filename, string.Empty),
190 Builders<GridFSFileInfo>.Filter.Gte(x => x.UploadDateTime, new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc)),
191 Builders<GridFSFileInfo>.Filter.Lt(x => x.UploadDateTime, new DateTime(2022, 2, 1, 0, 0, 0, DateTimeKind.Utc)));
192 var sort = Builders<GridFSFileInfo>.Sort.Descending(x => x.UploadDateTime);
193 var options = new GridFSFindOptions
194 {
195 //Limit = 1,
196 Sort = sort
197 };
198 List<MongoFile> lstFiles = new List<MongoFile>();
199 using (var cursor = bucket.Find(filter, options))
200 {
201 var fileInfos = cursor.ToList();
202 foreach (var fileInfo in fileInfos) {
203 MongoFile f = new MongoFile()
204 {
205 Id=fileInfo.Id,
206 name = fileInfo.Filename,
207 suffix = Path.GetExtension(fileInfo.Filename),
208 size = int.Parse(fileInfo.Length.ToString())
209 };
210 lstFiles.Add(f);
211 }
212 }
213 return lstFiles;
214 }
215
216 public List<MongoFile> FindFileByName(string fileName)
217 {
218 IGridFSBucket bucket = new GridFSBucket(db);
219 var filter = Builders<GridFSFileInfo>.Filter.And(
220 Builders<GridFSFileInfo>.Filter.Eq(x => x.Filename, fileName),
221 Builders<GridFSFileInfo>.Filter.Gte(x => x.UploadDateTime, new DateTime(2015, 1, 1, 0, 0, 0, DateTimeKind.Utc)),
222 Builders<GridFSFileInfo>.Filter.Lt(x => x.UploadDateTime, new DateTime(2015, 2, 1, 0, 0, 0, DateTimeKind.Utc)));
223 var sort = Builders<GridFSFileInfo>.Sort.Descending(x => x.UploadDateTime);
224 var options = new GridFSFindOptions
225 {
226 //Limit = 1,
227 Sort = sort
228 };
229 List<MongoFile> lstFiles = new List<MongoFile>();
230 using (var cursor = bucket.Find(filter, options))
231 {
232 var fileInfos = cursor.ToList();
233 foreach (var fileInfo in fileInfos)
234 {
235 MongoFile f = new MongoFile()
236 {
237 Id = fileInfo.Id,
238 name = fileInfo.Filename,
239 suffix = Path.GetExtension(fileInfo.Filename),
240 size = int.Parse(fileInfo.Length.ToString())
241 };
242 lstFiles.Add(f);
243 }
244 }
245 return lstFiles;
246 }
247 }
248 }


然后操作时,调用帮助类即可,如下所示:

查询调用



1        private void btnQuery_Click(object sender, EventArgs e)
2 {
3 string name = this.txtName.Text.Trim();
4 List<MongoFile> fileInfos = new List<MongoFile>();
5 if (string.IsNullOrEmpty(name))
6 {
7 fileInfos = helper.FindFiles();
8 }
9 else {
10 fileInfos = helper.FindFileByName(name);
11 }
12
13 this.dgView.AutoGenerateColumns = false;
14 this.bsView.DataSource = fileInfos;
15 this.dgView.DataSource = this.bsView;
16 }


下载调用



1         private void dgView_CellContentClick(object sender, DataGridViewCellEventArgs e)
2 {
3 if (e.ColumnIndex == 3) {
4 //第3个是下载按钮
5 SaveFileDialog sfd = new SaveFileDialog();
6
7 var file = (MongoFile)(this.dgView.Rows[e.RowIndex].DataBoundItem);
8 sfd.FileName = file.name;
9 sfd.Title = "请保存文件";
10 if (DialogResult.OK == sfd.ShowDialog())
11 {
12 helper.DownloadFile(file.Id,sfd.FileName);
13 MessageBox.Show("保存成功");
14
15 }
16 }
17 }


保存调用



1         private void btnSave_Click(object sender, EventArgs e)
2 {
3 string filePath = this.txtPath.Text;
4 if (!string.IsNullOrEmpty(filePath))
5 {
6 this.helper.UploadFile(filePath);
7 MessageBox.Show("保存成功");
8 }
9 else {
10 MessageBox.Show("请先选择文件");
11 }
12
13 }


MongoDB查询

当通过GridFS方式保存文件成功后,会在GridFS Buckets下生成fs对象,且在集合下生成两个集合【fs.files,fs.chunks】,用于存储文件,如下所示:

C# 玩转MongoDB(三)_二进制数_05

 

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

C# 玩转MongoDB(三)_MongoDB_06

 

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

C# 玩转MongoDB(三)_二进制数_07

 

 注意:如果文件太大,在fs.chunks集合中,进行分片存储,n表示存储的顺序。

 以上就是C#操作MongoDB大文件存储的相关内容,旨在抛砖引玉,共同进步。

备注

点绛唇·感兴

【朝代】宋代 【作者】王禹偁【chēng】


雨恨云愁,江南依旧称佳丽。水村渔市,一缕孤烟细。

天际征鸿,遥认行如缀。平生事,此时凝睇,谁会凭栏意。(栏 通:阑)


C# 玩转MongoDB(三)_C#_08