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 } 来做片键。

这就是今天全部的内容了。

posted @ 2023-01-31 15:46  81  阅读(563)  评论(0编辑  收藏  举报