Хранение файлов в базе данных и GridFS

Последнее обновление: 29.20.2022

Для хранения больших объемов информации, в частности, файлов, в MongoDB используется система GridFS. Для работы с ней прежде всего необходимо дополнительно установить через Nuget пакет MongoDB.Driver.GridFS:

пакет GridFS в MongoDB и C#

Для сохранения и получения из бд файлов, необходимо получить объект GridFS, определенный у базы данных. И для этого надо создать объект IGridFSBucket, определенный в пространстве имен MongoDB.Driver.GridFS. Для его создания можно использовать конструктор класса GridFSBucket, в который передается объект базы данных:

using MongoDB.Driver;
using MongoDB.Driver.GridFS;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
IGridFSBucket gridFS = new GridFSBucket(db);

Класс GridFSBucket предоставляет функциональность по управлению хранилищем, в частности, по добавлению, изменению и удалению файлов.

Сохранение файлов в базу данных

Для добавления файлов в GridFS используется ряд методов, которые позволяют загружать либо данные из потока, либо массив байтов:

  • UploadFromBytes/UploadFromBytesAsync: загружает файл в GridFS в виде массива байтов.

  • UploadFromStream/UploadFromStreamAsync: получает данные из потока

Например, загрузим файл с локальной машины в хранилище:

using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.GridFS;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
IGridFSBucket gridFS = new GridFSBucket(db);

// создаем поток из файла
using Stream fs = File.OpenRead("D:\\cats.jpg");
// сохраняем в бд
ObjectId id = await gridFS.UploadFromStreamAsync("cats.jpg", fs);
Console.WriteLine($"id файла: {id}");

С помощью метода File.OpenRead() создаем объект FileStream - файловый поток, а затем этот поток передается в метод gridFS.UploadFromStreamAsync() в качестве второго параметра. Первый параметр метода - имя файла - произвольный идентификатор. Метод возвращает id добавленного файла в GridFS в виде объекта ObjectId.

Если мы посмотрим содержание базы данных через MongoDBAtlas, то сможем увидеть в коллекции fs.files информацию о сохраненном файле:

Сохранение файлов в MongoDB и GridFS

Чтение файлов из GridFS

Для загрузки файла из GridFS также применяется ряд методов в зависимости от того, во что мы загружаем файл - в массив байтов или в поток:

  • DownloadToStream/DownloadToStreamAsync: загрузка файла по id из GridFS в поток

  • DownloadAsBytes/DownloadAsBytesAsync: загрузка файла по id в виде массива байтов

  • DownloadToStreamByName/DownloadToStreamByNameAsync: загрузка файла по имени из GridFS в поток

  • DownloadAsBytesByName/DownloadAsBytesByNameAsync: загрузка файла по id в виде массива байтов

Первые две пары методов в качестве первого параметра принимают id файла, который был присвоен при загрузке в GridFS. Третья пара методов загружает файла по имени. И четвертая пара методов загружает файл в виде массива байтов.

Извлечем ранее загруженный в GridFS файл:

using MongoDB.Driver;
using MongoDB.Driver.GridFS;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
IGridFSBucket gridFS = new GridFSBucket(db);

// создаем поток из файла
using Stream fs = File.OpenWrite("D:\\new_cats.jpg");
// извлекаем из бд в поток
await gridFS.DownloadToStreamByNameAsync("cats.jpg", fs);

В первом примере при загрузке в GridFS файлу было присвоено имя "cats.jpg". Теперь мы поэтому имени наоборот извлекаем файл из GridFS. Извлеченный файл передается в поток и сохраняется на диске D под именем "cats_new.jpg".

Подобным образом мы можем использовать перегрузки метода и загрузить файл, по определенному id. Например, загрузка по имени в массив байтов:

byte[] fileBytes = await gridFS.DownloadAsBytesByNameAsync("cats.jpg");

Загрузка по id в массив байтов:

byte[] fileBytes = await gridFS.DownloadAsBytesAsync(new ObjectId("635c2139a03205d6b1c9a88d"));

Загрузка по id в поток:

using Stream fs = File.OpenWrite("D:\\new_cats2.jpg");
await gridFS.DownloadToStreamAsync(new ObjectId("635c2139a03205d6b1c9a88d"), fs);

Поиск файла

С помощью методов Find/FindAsync мы можем найти файл в GridFS, а точнее получить различные его свойства. В качестве параметра эти методы принимают объект FilterDefinition<GridFSFileInfo>, который определяет критерии поиска.

Результатом методов Find/FindAsync является объект IAsyncCursor<GridFSFileInfo> - список информации о файлах. Используя объект GridFSFileInfo, мы можем извлечь информацию о файле с помощью ряда его свойств:

  • Id: идентификатор файла

  • Filename: имя файла

  • UploadDateTime: дата и время загрузки файла (в виде объекта DateTime) файла

  • Length: длина файла в байтах

  • BackingDocument: документ BsonDocument, который представляет данный файл

  • ChunkSizeBytes: размер одного чанка файла в байтах

Например, найдем информацию по ранее добавленному файлу cats.jpg:

using MongoDB.Driver;
using MongoDB.Driver.GridFS;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
IGridFSBucket gridFS = new GridFSBucket(db);

// создаем фильтр для поиска
var filter = Builders<GridFSFileInfo>.Filter.Eq(info => info.Filename, "cats.jpg");
// находим все файлы
var fileInfos = await gridFS.FindAsync(filter);
// получаем первый файл
var fileInfo = fileInfos.FirstOrDefault();
// выводим его id
Console.WriteLine($"id = {fileInfo?.Id}\nName: {fileInfo?.Filename}\n" +
    $"UploadDateTime: {fileInfo?.UploadDateTime}\nSize: {fileInfo?.Length}");
Console.WriteLine(fileInfo?.BackingDocument);

Фильтр для поиска создается тем же образом, что и фильтр для выборки из обычной базы данных MongoDB. При поиске мы указываем, по какому критерию надо находить файлы. В данном случае в качестве критерия используется имя файла. Информацию о файле представляет класс GridFSFileInfo, а имя файла хранится в его свойстве Filename, которое представляет строку. В метод Eq в качестве первого параметра передается метод, который возвращает свойство Filename. А второй параметр представляет значение, которое должно иметь это свойство.

Необязательно искать именно по имени файла. Можно было взять любое свойство класса GridFSFileInfo в качестве критерия.

По одному и тому же критерию в бд может находиться несколько файлов, поэтому метод Find фактически возвращает набор, из которого мы можем получить отдельные объекты GridFSFileInfo и их свойства. Так, в моем случае я получу следующий консольный вывод:

id = 635c2139a03205d6b1c9a88d
Name: cats.jpg
UploadDateTime: 28.10.2022 18:36:42
Size: 4043989
{ "_id" : ObjectId("635c2139a03205d6b1c9a88d"), "length" : NumberLong(4043989), "chunkSize" : 261120, "uploadDate" : ISODate("2022-10-28T18:36:42.165Z"), "md5" : "40c745065b62a00167c0d3768b25a3c7", "filename" : "cats.jpg" }

Замена файла

Для замены файла надо повторно его загрузить с помощью одного из методов UploadXXX и при загрузке указать параметр замены. Например, заменим файл по id:

using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.GridFS;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
IGridFSBucket gridFS = new GridFSBucket(db);

using FileStream fs = File.OpenRead("D:\\forest.jpg");
var id = await gridFS.UploadFromStreamAsync(
        "cats.jpg",
        fs,
        new GridFSUploadOptions { Metadata = new BsonDocument("filename", "cats.jpg") });

Console.WriteLine($"Файл заменен. Id файла: {id}");

Для замены в метод UploadFromStreamAsync передается объект GridFSUploadOptions, который описывает дополнительные опции загрузки. В частности, его свойство Metadata указывает на критерий замены. В данном случае замена будет производиться для файлов, у которых поле "filename" равно "cats.jpg".

Удаление файлов

Для удаления файла используются методы Delete/DeleteAsync, которые удаляют файл по id:

Например, удалим ранее сохраненный файл:

using MongoDB.Driver;
using MongoDB.Driver.GridFS;

MongoClient client = new MongoClient("mongodb://localhost:27017");

var db = client.GetDatabase("test"); 
IGridFSBucket gridFS = new GridFSBucket(db);
 
var filter = Builders<GridFSFileInfo>.Filter.Eq(info => info.Filename, "cats.jpg");
// находим все файлы по имени "cats.jpg"
var fileInfos = await gridFS.FindAsync(filter);
// получаем первый файл
var fileInfo = fileInfos.FirstOrDefault();

// если файл найден, удаляем его
if(fileInfo!= null)
    await gridFS.DeleteAsync(fileInfo.Id);
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850