Как вы уже знаете, MongoDB — это одно из наиболее развитых, open-source NoSQL решений, которое представляет собой документо-ориентированную базу данных, является кросс-платформенным, а также обеспечивает высокую производительность, доступность и простоту масштабирования.

В современных open-source веб-приложениях использование NoSQL решений получило свою популярность в связи с их нереляционным поведением. В данной статье шаг за шагом мы реализуем простое ASP.NET Core Web API приложение «записной книжки», которое будет поддерживать операции CRUD применимо к коллекции в MongoDB.

Почему MongoDB


Выбор СУБД зависит прежде всего от того, какое приложение вы хотите создать. Т.е. БД выбирают не разработчики, а сам продукт. Для работы с документами MongoDB будет отличным выбором. Ярким примером такого приложения будет блог или социальная сеть, где каждый автор может создавать записи, добавлять к ним изображения, видео и музыку. Другие же пользователи могут комментировать эти записи, а также оценивать их с помощью, например, оценок «Нравится» или «звездочной» системы. Но как вы будете хранить эти данные? Если вы знакомы с реляционными СУБД, вы можете представить, как будет выглядеть подобная схема:

image

Давайте представим структуру отдельной записи, а также способ её отображения. Для того чтобы получить запись и связанные с ней данные (изображения, аудио, видео, комментарии, оценки, сведения о пользователе и т.д.) нам нужно выполнить запрос к порядка восьми соединениям таблиц. А теперь представьте, какой поток записей (динамически загружаемый и передаваемый на тысячи клиентов) предстоит обрабатывать, и вы поймёте, что для реализации данной задачи необходимы тысячи запросов к большому количеству соединений таблиц. Конечно, для хранения данных мы можем использовать реляционную БД, например SQL Server, т.к. SQL поддерживает динамические данные в формате JSON. Но есть вариант, который упрощает подход при этом конкретном сценарии — это база данных NoSQL. Когда вы используете один документ, как показано ниже, сохраняя его в MongoDB, вы можете повысить производительность вашего приложения и получить запись полностью с помощью одного запроса без соединений таблиц. Это более простой и эффективный способ.

{
    "id":"ew12-res2-234e-544f",
    "title":"post title",
    "date":"2016-01-01",
    "body":"this is an awesome post stored on NoSQL",
    "createdBy":"User",
    "images":["http://example.com/myfirstimage.png","http://example.com/mysecondimage.png"],
    "videos":[
        {"url":"http://example.com/myfirstvideo.mp4", "title":"The first video"},
        {"url":"http://example.com/mysecondvideo.mp4", "title":"The second video"}
    ],
    "audios":[
        {"url":"http://example.com/myfirstaudio.mp3", "title":"The first audio"},
        {"url":"http://example.com/mysecondaudio.mp3", "title":"The second audio"}
    ]
}

Кроме того, использование MongoDB позволяет разработчикам ослаблять согласованность, создавая приложения с высоким уровнем доступности и низкой задержкой.

Прошу заметить, что не совсем корректно называть MongoDB заменой реляционных БД, это скорее альтернатива. Данный инструмент может делать то же, что могут делать множество других — что-то лучше, а что-то нет.

Бесструктурная архитектура коллекций в MongoDB не обязывает разработчиков определять и обслуживать схемы на уровне данных, обеспечивая быструю итерацию при разработке продукта. Вам надо сохранить объект? Сериализуйте его в BSON и отправьте в MongoDB. Нет никакого маппинга свойств или типов.

Эта простота определенно может подходить вам как конечному разработчику.

До версии 1.8 MongoDB считалась ненадежной в плане хранилища, однако в этой версии был добавлен механизм журналирования, который по умолчанию уже включен, но его можно отключить в файле конфигурации. Несмотря на некоторое увеличение производительности, при отключенном механизме журналирования есть определенный риск потери данных.

Технологический стек


Для хранения данных MongoDB использует документы и схему базы данных в формате BSON (Binary JavaScript Object Notation), который является надмножеством формата JSON, включая дополнительно регулярные выражения, двоичные данные и даты. Схема в MongoDB называется коллекцией, а запись в этой схеме называется документом.

image

ASP.NET Core Web API имеет большое преимущество, потому что он может использоваться в качестве HTTP сервиса и может быть использован любым клиентским приложением, начиная с настольных компьютеров, заканчивая мобильными телефонами, а также может быть установлен на Windows, MacOS и Linux.

Примечание: на текущий момент MongoDB не поддерживается в EF Core. Достаточно мало шансов, что грядущие версии EF Core будут поддерживать MongoDB (на основе комментариев Роуэна Миллера), но данный функционал может быть реализован в будущем.

Ниже приводятся все необходимые для работы компоненты:


Конфигурация MongoDB


После того как мы установили MongoDB, нам нужно настроить доступ к БД, а также указать, где расположены данные. Для этого создадим файл mongod.cfg. Этот файл будет содержать путь к папке с данными сервера MongoDB, а также файл журнала MongoDB, первоначально без каких-либо проверок подлинности.

systemLog:
  destination: file
  path: "C:\\data\\db\\log\\mongo.log"
  logAppend: true
storage:
  dbPath: "C:\\data\\db"

Примечание: по умолчанию MongoDB пытается инициализировать директорию БД по адресу "C:\\data\\db" и выдает ошибку, если не может определить данный адрес.

Затем запускаем в командной строке следующую команду (необходимо актуализировать адреса согласно местоположению файла конфигурации и местоположения папки, куда установлена MongoDB).
"C:\Program Files\MongoDB\Server\3.4\bin\mongod.exe" --config C:\Dev\mongod.cfg
Эта команда запустит сервер MongoDB, указывая на уже созданный файл конфигурации.

Примечание: Указание абсолютного пути к бинарному файлу является не очень хорошим тоном. Для интенсивного использования данного адреса, предлагается добавить путь к каталогу в системную переменную PATH.

После того, как сервер запущен (вы можете увидеть детали в файле логов), запустим клиент mongo.exe из командной строки:

image
Примечание: клиент может выдать предупреждение о том, что не настроен контроль доступа (далее мы это исправим):

image

Создадим учетную запись администратора, выполнив следующие команды в консольном клиенте:

use admin
db.createUser(
  {
	user: "admin",
	pwd: "abc123!",
	roles: [ { role: "root", db: "admin" } ]
  }
);
exit;

Затем остановим сервер и обновим файл конфигурации mongod.cfg, включая параметр безопасности:

systemLog:
  destination: file
  path: "C:\\data\\db\\log\\mongo.log"
  logAppend: true
storage:
  dbPath: "C:\\data\\db"
security:
  authorization: enabled

С этого момента мы подключаемся к MongoDB используя пользователя admin.

Примечание: Рекомендуется не использовать роль суперпользователя (в нашем случае — администратора) для обычных операций, но для того, чтобы сохранить простоту, мы будем по-прежнему использовать только одного пользователя.

Создание ASP.NET Web API проекта


Готовую реализацию можно скачать с GitHub.

Запустим Visual Studio, File > New Project > .Net Core > ASP.NET Core Web Application.

image

Затем выберем шаблон Web API, OK.

image

Конфигурация проекта


Существует несколько поддерживаемых форматов файла конфигурации (JSON, XML или ini), и по умолчанию шаблон проекта Web API разворачивается с конфигом в формате JSON. Обновим файл AppSettings.json, добавив в него информацию о подключении к базе данных.

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "Microsoft": "Information",
      "System": "Information"
    }
  },
  "MongoConnection": {
    "ConnectionString": "mongodb://admin:abc123!@localhost",
    "Database": "NotesDb"
  }
}

Внедрение зависимости и модель параметров


Внедрение конструктора (Constructor Injection) является одним из наиболее распространенных подходов к внедрению зависимости (Dependency Injection). ASP.NET Core использует внедрение конструктора в своём решении, поэтому мы также будем использовать его. Чтобы замапить параметры подключения к БД, мы добавим новый класс настроек.

namespace NotebookAppApi.Model
{
    public class Settings
    {
        public string ConnectionString;
        public string Database;
    }
}

Модифицируем метод ConfigureSettings в файле Startup.cs чтобы добавить наши настройки в модель доступа к параметрам. В дальнейшем мы будем использовать интерфейс IOptions для доступа к настройкам.

public void ConfigureServices(IServiceCollection services)
{
    // Add framework services.
    services.AddMvc();

    services.Configure<Settings>(options =>
    {
        options.ConnectionString = Configuration.GetSection("MongoConnection:ConnectionString").Value;
        options.Database = Configuration.GetSection("MongoConnection:Database").Value;
    });
}

MongoDB .NET Driver


Для того чтобы подключиться к MongoDB, добавим в проект Nuget пакет под названием MongoDB.Driver. Это официальный драйвер для .NET с полной поддержкой приложений на ASP.NET Core.

image

Модель


Модель класса (POCO), которая описывает каждую заметку в нашей записной книжке приведена ниже.

using System;
using MongoDB.Bson.Serialization.Attributes;

namespace NotebookAppApi.Model
{
    public class Note
    {
        [BsonId]
        public string Id { get; set; }
        public string Body { get; set; } = string.Empty;
        public DateTime UpdatedOn { get; set; } = DateTime.Now;
        public DateTime CreatedOn { get; set; } = DateTime.Now;
        public int UserId { get; set; } = 0;
    }
}

Контекст базы данных


Реализуем паттерн Unit of Work в классе NoteContext:

public class NoteContext
{
    private readonly IMongoDatabase _database = null;

    public NoteContext(IOptions settings)
    {
        var client = new MongoClient(settings.Value.ConnectionString);
        if (client != null)
            _database = client.GetDatabase(settings.Value.Database);
    }

    public IMongoCollection Notes
    {
        get
        {
            return _database.GetCollection("Note");
        }
    }
}

Репозиторий


Используя интерфейс репозитория, мы реализуем функционал, необходимый для управления нашими заметками. Они также будут использовать внедрение зависимостей (DI) для простого доступа к приложению (например, раздел контроллера).

 public interface INoteRepository
    {
        Task<IEnumerable<Note>> GetAllNotes();
        Task<Note> GetNote(string id);
        Task AddNote(Note item);
        Task<DeleteResult> RemoveNote(string id);
        // обновление содержания (body) записи
        Task<UpdateResult> UpdateNote(string id, string body);
    }

Доступ к БД будет асинхронным. Мы используем драйвер не ниже версии 2.0, в которой был добавлен полный асинхронный стек. Как пример: чтобы получить все заметки, мы делаем асинхронный запрос:

public async Task<IEnumerable<Note>> GetAllNotes()
{
    return await _context.Notes.Find(_ => true).ToListAsync();
}

Ниже представлена полная реализация для основных CRUD операций:

namespace NotebookAppApi.Data
{
    public class NoteRepository : INoteRepository
    {
        private readonly NoteContext _context = null;

        public NoteRepository(IOptions<Settings> settings)
        {
            _context = new NoteContext(settings);
        }

        public async Task<IEnumerable<Note>> GetAllNotes()
        {
            return await _context.Notes.Find(_ => true).ToListAsync();
        }

        public async Task<Note> GetNote(string id)
        {
            var filter = Builders<Note>.Filter.Eq("Id", id);
            return await _context.Notes
                            .Find(filter)
                            .FirstOrDefaultAsync();
        }

        public async Task AddNote(Note item)
        {
            await _context.Notes.InsertOneAsync(item);
        }

        public async Task<DeleteResult> RemoveNote(string id)
        {
            return await _context.Notes.DeleteOneAsync(
                 Builders<Note>.Filter.Eq("Id", id));
        }

        public async Task<UpdateResult> UpdateNote(string id, string body)
        {
            var filter = Builders<Note>.Filter.Eq(s => s.Id, id);
            var update = Builders<Note>.Update
                            .Set(s => s.Body, body)
                            .CurrentDate(s => s.UpdatedOn);

            return await _context.Notes.UpdateOneAsync(filter, update);
        }
    }
}

Чтобы получить доступ к NoteRepository с помощью модели DI, добавим строку в ConfigureServices:

services.AddTransient<INoteRepository, NoteRepository>();

где:
  • AddTransient означает, что репозиторий создаётся заново каждый раз.
  • AddScoped — только один раз на один запрос.
  • AddSingleton — один раз при первом запросе. Каждый последующий запрос использует экземпляр, который был создан в первый раз.

Основной контроллер


Ниже представлен код основного контроллера нашего Web API приложения, где реализованы все CRUD методы, которые доступны сторонним приложениям.

Примечание: метод Get помечен атрибутом NoCache, чтобы убедиться, что веб-клиенты всегда делают запрос к серверу.

namespace NotebookAppApi.Controllers
{
    [Produces("application/json")]
    [Route("api/[controller]")]
    public class NotesController : Controller
    {
        private readonly INoteRepository _noteRepository;

        public NotesController(INoteRepository noteRepository)
        {
            _noteRepository = noteRepository;
        }

        [NoCache]
        [HttpGet]
        public Task<IEnumerable<Note>> Get()
        {
            return GetNoteInternal();
        }

        private async Task<IEnumerable<Note>> GetNoteInternal()
        {
            return await _noteRepository.GetAllNotes();
        }

        // GET api/notes/5
        [HttpGet("{id}")]
        public Task<Note> Get(string id)
        {
            return GetNoteByIdInternal(id);
        }

        private async Task<Note> GetNoteByIdInternal(string id)
        {
            return await _noteRepository.GetNote(id) ?? new Note();
        }

        // POST api/notes
        [HttpPost]
        public void Post([FromBody]string value)
        {
            _noteRepository.AddNote(new Note() { Body = value, CreatedOn = DateTime.Now, UpdatedOn = DateTime.Now });
        }

        // PUT api/notes/5
        [HttpPut("{id}")]
        public void Put(string id, [FromBody]string value)
        {
            _noteRepository.UpdateNoteDocument(id, value);
        }

        // DELETE api/notes/23243423
        [HttpDelete("{id}")]
        public void Delete(string id)
        {
            _noteRepository.RemoveNote(id);
        }
    }
}

Инициализация данных


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

Реализуем один метод Init, который создаст и наполнит БД всеми необходимыми тестовыми данными. Метод будет доступен по адресам http://localhost:5000/api/system/init или http://localhost:53617/api/system/init (в случае, если мы используем IIS). На практике такой контроллер мог бы эволюционировать в полноценную «админку».

[Route("api/[controller]")]
public class SystemController : Controller
{
    private readonly INoteRepository _noteRepository;

    public SystemController(INoteRepository noteRepository)
    {
        _noteRepository = noteRepository;
    }

    // Call an initialization - api/system/init
    [HttpGet("{setting}")]
    public string Get(string setting)
    {
        if (setting == "init")
        {
            _noteRepository.RemoveAllNotes();
            _noteRepository.AddNote(new Note() { Id = "1", Body = "Test note 1", 
                          CreatedOn = DateTime.Now, UpdatedOn = DateTime.Now, UserId = 1 });
            _noteRepository.AddNote(new Note() { Id = "2", Body = "Test note 2", 
                          CreatedOn = DateTime.Now, UpdatedOn = DateTime.Now, UserId = 1 });
            _noteRepository.AddNote(new Note() { Id = "3", Body = "Test note 3", 
                          CreatedOn = DateTime.Now, UpdatedOn = DateTime.Now, UserId = 2 });
            _noteRepository.AddNote(new Note() { Id = "4", Body = "Test note 4", 
                          CreatedOn = DateTime.Now, UpdatedOn = DateTime.Now, UserId = 2 });

            return "Done";
        }

        return "Unknown";
    }
}

Настройки запуска


Для того чтобы отображать результаты запросов на сервер при запуске проекта, обновим файл LaunchSettings.json

image

Ниже приводится пример настройки, при которой по умолчанию запускается URL-адрес с отображением всех заметок (api/notes).

{
  "iisSettings": {
    "windowsAuthentication": false,
    "anonymousAuthentication": true,
    "iisExpress": {
      "applicationUrl": "http://localhost:53617/",
      "sslPort": 0
    }
  },
  "profiles": {
    "IIS Express": {
      "commandName": "IISExpress",
      "launchBrowser": true,
      "launchUrl": "api/notes",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    },
    "NotebookAppApi": {
      "commandName": "Project",
      "launchBrowser": true,
      "launchUrl": "http://localhost:5000/api/notes",
      "environmentVariables": {
        "ASPNETCORE_ENVIRONMENT": "Development"
      }
    }
  }
}

Запуск проекта


Перед запуском проекта убедитесь в том, что MongoDB запущена (как служба Windows, либо из командной строки, как это было сделано выше).

При первом запуске приложения нас перенаправит на страницу со всеми записями, но т.к. мы еще не запустили процесс инициализации, на данной странице будет отображаться пустое множество.

image

Запустим команду на инициализацию БД и заполнение ее тестовыми данными, перейдя по адресу.

image

Затем, вернемся на стартовую страницу приложения и увидим, что данные появились:

image

Использование Robomongo


С помощью Robomongo можно проверить наличие записей в базе данных. Подключившись к базе данных с использованием учетных данных мы можем увидеть все 4 записи.

image

Обработка исключений


В C# 5.0 были введены ключевые слова async и await, которые упрощают использование TPL (Task Parallel Library). Мы можем просто использовать блок try/catch для перехвата исключений.public async Task<IEnumerable> GetAllNotes()

{
    try
    {
        return await _context.Notes.Find(_ => true).ToListAsync();
    }
    catch (Exception ex)
    {
        // log or manage the exception
        throw;
    }
}

Так мы можем обрабатывать ошибки в асинхронных экземплярах Task, при этом можно пробрасывать исключение выше для дальнейшей обработки.

Полное обновление документа MongoDB


Изначально в примере проекта функция обновления работала по принципу выборочного обновления свойства.

С помощью метода ReplaceOneAsync мы можем обновить документ полностью. Параметр IsUpsert указывает, что в случае отсутствия документа в БД, его необходимо создать.

public async Task<ReplaceOneResult> UpdateNote(string id, Note item)
{
     return await _context.Notes
                          .ReplaceOneAsync(n => n.Id.Equals(id)
                                            , item
                                            , new UpdateOptions { IsUpsert = true });
}

Настройка кросс-доменных запросов (CORS)


Для работы с приложениями, работающими на разных доменах, у нашего решения на ASP.NET Web API необходимо разрешить обработку кросс-доменных запросов (CORS). Например, Angular 2 перед основным запросом сначала выполняет pre-check запрос, который служит проверкой того, разрешены ли междоменные запросы. Для начала, зарегистрируем функционал CORS в методе ConfigureServices файла Startup.cs:

public void ConfigureServices(IServiceCollection services) 
 {
      // Add service and create Policy with options 
      services.AddCors(options => { options.AddPolicy("CorsPolicy", 
                                      builder => builder.AllowAnyOrigin() 
                                                        .AllowAnyMethod() 
                                                        .AllowAnyHeader() 
                                                        .AllowCredentials()); 
                                  }); 
      // .... 

      services.AddMvc(); 
 }

Затем включим политику глобально для каждого запроса в приложении, путем вызова метода app.useCors в методе Configure:

public void Configure(IApplicationBuilder app)
{
    app.UseCors("CorsPolicy");
    app.UseMvc();
}

Работа с файлами


В документе MongoDB также можно хранить бинарные файлы, но максимальный размер документа в MongoDB 2.x составляет 16 Мб (в более ранних версиях — лишь 4 Мб). Поэтому, для хранения больших объемов информации, в частности файлов больших размеров, в MongoDB используется система GridFS. GridFS — это соглашение о хранении файлов произвольного размера в MongoDB, поддерживаемое всеми официальными драйверами. Файлы в БД хранятся в двух коллекциях, обычно называемых «fs.files» и «fs.chunks». Каждый загруженный файл имеет один документ в коллекции «fs.files», содержащий информацию о файле и множество документов-кусков (чанков) в коллекции «fs.chunks», которая хранит содержимое файла по частям.

Для начала нам нужно добавить компоненты GridFS для драйвера через NuGet.

image

Затем нам необходимо добавить объект класса GridFSBucket в наш контекст. GridFSBucket — ключевой объект для работы с GridFS, являющийся своего рода комбинацией коллекций «fs.files» и «fs.chunks» и представляет собой «ведро» (Bucket), в котором могут храниться все наши файлы.

_database = client.GetDatabase(settings.Value.Database);
_bucket = new GridFSBucket(_database);

Вы также можете указать дополнительные параметры. Например, название коллекции (вместо префикса «fs» будет ваше название) и размер одного чанка.

 _database = client.GetDatabase(settings.Value.Database);
 var gridFSBucketOptions = new GridFSBucketOptions()
 {
     BucketName = "images",
     ChunkSizeBytes = 1048576, // 1МБ
 };
 _bucket = new GridFSBucket(_database, gridFSBucketOptions);

И добавим возможность использовать наш к экземпляр GridFSBucket:

public GridFSBucket Bucket
{
    get
    {
        return _bucket;
    }
}

Или можно использовать более краткую запись:

public GridFSBucket Bucket => _bucket;

Теперь обновим наш функционал. Добавим метод добавления файла в БД в NoteRepository.cs

     public async Task<ObjectId> UploadFile(IFormFile file)
        {
            try
            {
                var stream = file.OpenReadStream();
                var filename = file.FileName;
                return await _context.Bucket.UploadFromStreamAsync(filename, stream);
            }
            catch (Exception ex)
            {
                return new ObjectId(ex.ToString());
            } 
        }

Примечание: Следует всегда использовать объект GridFSBucket для взаимодействия с коллекциями GridFS («fs.chunks» и «fs.files») вместо непосредственного обращения к этим коллекциям.

Добавим сигнатуру метода в INoteRepository.cs:

Task<ObjectId> UploadFile(IFormFile file);

Обновим NotesController.cs, добавив в него сам POST метод:

// POST api/notes/uploadFile
[HttpPost("uploadFile")]
public async Task<ObjectId> UploadFile(IFormFile file)
{
    return await _noteRepository.UploadFile(file);
}

Примечание: Для корректной обработки данных, передающихся с заголовком Content-Type: multipart/form-data (так мы будем передавать файлы) необходимо добавить поддержку данных такого типа. Для этого мы добавим для класса контроллера NotesController соответствующий атрибут Consumes:

[Consumes("application/json", "multipart/form-data")]
public class NotesController : Controller

Получение информации о файле рассмотрим на примере простого метода, который по Id файла будет возвращать его название:

       public async Task<String> GetFileInfo(string id)
        {
            GridFSFileInfo info = null;
            var objectId = new ObjectId(id);
            try
            {
                using (var stream = await _context.Bucket.OpenDownloadStreamAsync(objectId))
                {
                    info = stream.FileInfo;
                }
                return info.Filename;
            }
            catch (Exception)
            {
                return "Not Found";
            }
        }

Проверим наш API


Для проверки работы Web API будем использовать Postman. Например, проверим получение записи по Id (api/notes/2) Выберем тип запроса GET, затем введем URL (http://localhost:53617/api/notes/2) и добавим заголовок (Content-Type: application/json). После отправки запроса мы получаем ответ в виде информации о записи с Id = 2.
image

Поменяем содержимое данной записи. Для этого поменяем тип запроса на PUT, затем перейдем на вкладку Body > Raw и зададим новое содержание записи.
image

После этого зайдем в RoboMongo и убедимся, что значение записи обновилось.

image

Проверим работу механизма GridFS. Для этого поменяем Content-Type на multipart/form-data, URL зададим как localhost:53617/api/notes/uploadFile и в разделе Body > form-data добавим и отправим файл. В ответ мы получили информацию о данном файле:
image

После этого для данного файла создались соответствующие записи в коллекциях «images.files» и «images.chunks».

image

Проверим наш простой метод получения имени файла по его ID. Для этого сделаем обычный GET запрос с передачей ID файла в адресной строке.

image

Заключение


Сам факт того, что команда MongoDB уделяет достаточно внимания удобству и полезности разработчикам, проводя обучающие курсы, активно интересуясь мнением сообщества и оперативно решая возникающие проблемы (проект пишут не только волонтеры, но и компания людей на полной занятости), дает возможность утверждать, что MongoDB и дальше будет сохранять отличный темп внедрения новых возможностей и совершенствоваться.

И то, что MongoDB можно развернуть не только на Windows, в совокупности с достаточной зрелостью драйвера и курсом Microsoft на поддержку .NET Core в Linux и MacOS, является ощутимым плюсом, например, если вы хотите развернуть ваше решение полностью на Linux. Однако, нельзя не упомянуть про открытое тестирование SQL Server 2017, данное решение также можно развернуть на Linux. И выбор подхода, который Вам нужно использовать при разработке проекта: NoSQL или RDBMS — зависит в основном от специфики Вашего приложения. MongoDB идеально подойдёт там, где все сценарии работы с данными укладываются и будут укладываться в будущем в выборки по ключу и получение всей коллекции. Если такой уверенности нет, то лучше сразу взять РСУБД.

Источники:

  1. http://www.qappdesign.com/using-mongodb-with-net-core-webapi/
  2. http://jsman.ru/mongo-book/Vvedenie.html
  3. http://mongodb.github.io/mongo-csharp-driver/2.4/reference/
  4. https://docs.microsoft.com/ru-ru/azure/documentdb/documentdb-nosql-vs-sql
Поделиться с друзьями
-->

Комментарии (11)


  1. apcehypo
    18.05.2017 14:12
    +1

    Как обстоит дело с производительностью? Слышал, что Mongo давно уступила первенство Postgres NoSQL…
    Тут, например, смотрел.
    Или уже что-то изменилось с тех пор?


    1. SSul
      18.05.2017 18:01

      Из сравнений наиболее актуальных версий (PostgreSQL 9.5b1 и MongoDB 3.2.0) можно упомянуть доклад на PgConf.Russia 2016 где рассматриваются результаты выполнения основных операций и рассказывается, почему и при каких условиях так получилось.:)
      С нетерпением ждём сравнение производительности MongoDB 3.4 и PosgreSQL 9.6!


  1. Nonsense66
    18.05.2017 14:12
    +1

    Небольшое замечание: в реализации методов репозитория можно напрямую возвращать Task или Task без async/await. Если же async/await необходим, рекомендуется await SomeJob().ConfigureAwait(false); — просто хорошая практика, почитайте в гугл.


    1. SSul
      18.05.2017 15:49

      Спасибо за комментарий, обязательно учтём в следующих публикациях!
      P.S. информацию по этой теме также можно почитать на хабре.


    1. ZOXEXIVO
      18.05.2017 23:07

      Это вам гугл сказал, что это хорошая практика? Вы весь свой код уже утыкали ConfigureAwait(false)?
      Разобрались бы сначала где и для чего это используется, чем вот так вот советовать, ссылаясь на Google


    1. wlbm_onizuka
      19.05.2017 06:22
      +1

      .ConfigureAwait(false); нужен чтобы избежать дедлока при вызове .Wait() из потока имеющего контекст (WPF или ASP.Net).

      Утыкивая свой код .ConfigureAwait(false); вы делаете его длинным и некрасивым ради того, чтобы кто-то мог использовать порочные практики вызывая .Wait в асинхронном коде. К сожалению это повсевместно рекомендуемое решение…

      Подробнее по ссылке: https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html


      1. wlbm_onizuka
        19.05.2017 06:27

        прошу прощения за оформление ссылки
        blog.stephencleary.com/2012/07/dont-block-on-async-code


      1. ZOXEXIVO
        19.05.2017 15:34

        Если вы разрабатываете библиотеки и не контролируете вызываемый код, тогда это нужно делать.
        Обычный код засорять такими вещами совершенно не нужно и если у вас есть товарищи, которые используют порочные практики — заворачивайте их на ревью.


    1. ilya-chumakov
      20.05.2017 18:41

      Вообще-то в статье речь про ASP.NET Core. Где SynchronizationContext отсутствует в принципе. Поэтому ConfigureAwait(false) имеет смысл только из соображений обратной совместимости с classic ASP.NET и проч.
      P.S. Подкрепить свои слова ссылкой — и куда более весомо, и куда вежливей, чем посылание в гугл.


  1. ZOXEXIVO
    18.05.2017 22:15
    +2

    Спасибо за статью, новичкам будет полезно, но есть несколько замечаний:

    1) services.Configure(options =>
    Зачем выковыривать каждую настройку, если сделали удобный маппинг всего объекта на JSON?

    2) Для connectionString достаточно одной строки и
    new MongoUrl(«mongodb://localhost:27017/test») спокойно вам отдаст и connectionString и DatabaseName

    3) services.AddTransient<INoteRepository, NoteRepository>()
    MongoClient — это SMART клиент, который нужен всего один на приложение. (исключения составляют особые случаи). MongoClient сам заботится о переподключении, о connection pool и.т.п, поэтому регайте NoteContext как Singleton.

    4) Builders.Filter.Eq(«Id», id) превращается в обычную лямбду (x => x.Id == id). Драйвер нормально типизирован и вполне современный


  1. Magic_B
    19.05.2017 10:23
    +1

    services.AddTransient<INoteRepository, NoteRepository>();

    Я бы все-таки сделал Scoped, если храните какой-то кеш на запрос, либо, как отметил ZOXEXIVO, Singleton. Раньше по-крайней мере клиент к монге именно так работал и не было смысла создавать несколько инстанцев коннекта, в последних версиях может что-то и поменялось, не смотрел...


    По поводу async-методов, не стоит реализовывать в репозитории только такие методы, они парами должны быть доступны (UpdateNote и UpdateNoteAsync), дело в том, что механизм выполнения асинхронных методов сложен и требует больших затрат в приложении, а, к примеру, достать запись по Id, выполнится быстрее, чем само создание объекта Task (условно). И помните, что таким образом Вы раздуваете конечный автомат (см. ссылку)