MongoDB GridFS操作说明
原文:https://www.sohu.com/a/457367447_468635
搭建基于MongoDB的文件管理系统(一) - 作业部落 Cmd Markdown 编辑阅读器 (zybuluo.com)
一、MongoDB GridFS
先说说 GridFS。
MongoDB 是用 Bson 来存储数据的,每一行数据,称为 Document。每个 Document,大小有个上限,是16M,也就是说,结构化数据量大的空间占用是16M。注意,这个16M不是简单的内容总和,因为 Bson 对于字段名和类型有一定的特殊处理,实际存储的内容在计算上或多或少会有些变化,真正限制的是存储 Bson 的16M。
对于超过16M的数据,比方一个图片文件,MongoDB 也提供了一种存储方式,就是 GridFS。
不同于常规的结构数据存储,MongoDB 在存储 GridFS 数据时,用了两个集合(Collection):fs.files 和 fs.chunks。files 集合用来存储文件的相关信息,chunks 集合用来存储真正的文件块。
在SDK早期,这两个集合可以独立操作(因为这两个文件本身也是可操作的集合)。但实际应用中,这带来了相当程度的混乱。因此,在SDK 2.0以后,加入了一个桶(Bucket)的概念。从此,操作GridFS的流程变成了:开发者对「桶」进行操作,而「桶」在系统内部进行对两个集合的操作。
这就是上面说的过期代码的问题。
桶(Bucket)在MongoDB 中是个概念,对应着两个集合 files 和 chunks。桶的默认名称是 fs,对应的两个集合是 fs.files 和 fs.chunks。我们也可以给桶命名,例如 Test,则对应的集合会是 Test.files 和 Test.chunks。
二、操作GridFS
在操作GridFS时,我们会直接对桶(Bucket)操作。桶是建立在数据库 Database 上的。
1. 前置操作
要使用GridFS,我们需要引入一个库:
% dotnet add package MongoDB.Driver.GridFS
这个库是MongoDB官方的Dotnet库。
引入这个库时,系统会加入以下四个库:
- MongoDB.Bson
- MongoDB.Driver
- MongoDB.Driver.Core
- MongoDB.Driver.GridFS
熟悉MongoDB开发的会清楚,前三个是结构化操作需要引入的库。而第四个,就是 GridFS 操作的库。
2. 打开Bucket
其实我更愿意叫连接。就是连接到一个桶的意思。
var client = newMongoClient(connection_string);;
var database = client.GetDatabase( "TestDatabase");
var bucket = newGridFSBucket(database);
三行代码,就连接到了一个Bucket。
如果需要连接到一个指定名称的Bucket,可以用下面的代码:
var bucket = newGridFSBucket(database, newGridFSBucketOptions
{
BucketName = "Test",
});
3. 上传文件
上传文件,Bucket提供了两个方法
- UploadFromBytes
- UploadFromStream
以及对应的异步方法:
- UploadFromBytesAsync
- UploadFromStreamAsync
此外,还提供了一组流式操作方法:
- OpenUploadStream
- OpenUploadStreamAsync
这其实就是一个简单的IO操作:
using(var fs = newFileStream(file_path, FileMode.Open))
{
var id = await bucket.UploadFromStreamAsync(file_name, fs);
}
成功后会返回ObjectId。
方法里的file_name,对应保存到files里的文件名filename。其实它是一个标识,用来让你查找文件用的。这个标识对应一个索引,是 { "filename" : 1, "uploadDate" : 1 }。对于相同的文件名,MongoDB视为同一个文件的多个版本,并通过uploadDate来区分版本。
我们来看一下保存后的数据:
Test.files
{
"_id": ObjectId( "60583228d37a5aec3c011557"),
"length": 73268,
"chunkSize": 261120,
"uploadDate": ISODate( "2021-03-22T13:59:05.278+0800"),
"md5": "f2fe3c4e2828082ad9e82a11fabe6dd0",
"filename": "test.jpg"
}
Test.chunks
{
"_id": ObjectId( "60583229d37a5aec3c011558"),
"files_id": ObjectId( "60583228d37a5aec3c011557"),
"n": 0,
"data": BinData( 0, "/9j/...")
}
这是对应的两个集合里的一条数据。上面说的返回的ObjectId,是files里的_id值。
实际应用时,files集合里记录的文件信息有点少,我们需要加一些我们自己想存入的信息。这时候,可以这么写:
var options = newGridFSUploadOptions
{
Metadata = newBsonDocument
{
{ "width", "1024"},
{ "height", "768"}
}
};
var id = await bucket.UploadFromStreamAsync(file_name, fs, options);
我们通过Metadata,把我们自己的数据也存到了files表里。
再看一下数据:
{
"_id" : ObjectId("60583228d37a5aec3c011557"),
"length" : 73268,
"chunkSize" : 261120,
"uploadDate" : ISODate("2021-03-22T13:59:05.278+0800"),
"md5" : "f2fe3c4e2828082ad9e82a11fabe6dd0",
"filename" : "test.jpg",
"metadata" : {
"width" : "1024",
"height" : "768"
}
}
4. 下载文件
同上传类似,一组直接方法:
- DownloadAsBytes
- DownloadAsBytesAsync
- DownloadToStream
- DownloadToStreamAsync
- DownloadAsBytesByName
- DownloadAsBytesByNameAsync
- DownloadToStreamByName
- DownloadToStreamByNameAsync
以及一组流式方法:
- OpenDownloadStream
- OpenDownloadStreamAsync
- OpenDownloadStreamByName
- OpenDownloadStreamByNameAsync
内容上跟上传相似,多了一类用名称查找的方法。
看个简单的例子:
using(var fs = newFileStream(save_file_name, FileMode.Create))
{
await bucket.DownloadToStreamAsync( newObjectId( "60583228d37a5aec3c011557")), fs);
}
给出ID,就是上面保存时返回的ID值,就可以下载文件到本地。
如果需要根据文件名下载,是这样的:
using(var fs = newFileStream(save_file_name, FileMode.Create))
{
await bucket.DownloadToStreamByNameAsync( "test.jpg", fs);
}
这样,我们就下载到文件的最新版本了。如果想获取文件的其它版本,可以加一个参数:
using(var fs = newFileStream(save_file_name, FileMode.Create))
{
await bucket.DownloadToStreamByNameAsync( "test.jpg", fs, newGridFSDownloadByNameOptions
{
Revision = 0
});
}
5. 查找文件
查找文件跟结构化数据的查询没有区别,唯一的是引用的定义不同。
var filter = Builders<GridFSFileInfo>.Filter.Eq(x => x.Filename, "test.jpg");s
var sort = Builders<GridFSFileInfo>.Sort.Descending(x => x.UploadDateTime);
var options = newGridFSFindOptions
{
Limit = 1,
Sort = sort
};
using(var cursor = bucket.Find(filter, options))
{
var fileInfo = cursor.ToList.FirstOrDefault;
}
这个不详细说了,一看就明白。
6. 删除文件
也很简单,根据ID直接删。
删除一个文件:
bucket.Delete( newObjectId( "60583228d37a5aec3c011557"));
或
await bucket.DeleteAsync( newObjectId( "60583228d37a5aec3c011557"));
7. 删除Bucket
这也是一个方法:
bucket.Drop;
或
await bucket.DropAsync;
这是一次性删除存在Bucket中的所有数据的最快方法。
三、分片
要想用好MongoDB集群,就得玩好分片。放到MongoDB集群里的GridFS,也需要分片。
不过,GridFS分片很简单。
GridFS有两个集合,files 和 chunks。两个集合数据大小完全不一样。
files 集合只包含元数据,数据占用空间不大。如果没有特殊原因,可以不分片。如果一定要分片,用_id做片键就好,或者用自己存储的信息字段,也可以。
chunks 包含文件块,数据占用空间很大,需要分片。分片时,可以用 { files_id : 1, n : 1} 做片键,也可以就直接用 { files_id : 1 } 做片键。对于MongoDB 4.0及以上的版本,还可以用 { files_id : "hashed", n : 1 } 来做片键。
这就是今天全部的内容了。