Для хранения больших объемов информации, в частности, файлов, в MongoDB используется система GridFS. Для работы с ней прежде всего необходимо дополнительно установить через Nuget пакет MongoDB.Driver.GridFS:
Для сохранения и получения из бд файлов, необходимо получить объект 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 информацию о сохраненном файле:
Для загрузки файла из 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);