Трудящийся программист

Переход к NoSQL с помощью MongoDB, часть 3

Тэд Ньюард (Ted Neward)

Загрузка примера кода

Ted NewardВ последний раз я продолжил изучение MongoDB с помощью исследовательских тестов. Я описал запуск и остановку сервера во время выполнения теста, затем продемонстрировал запись ссылок между документов и обсудил некоторые причины громоздкости этого. Теперь пришло время рассмотреть некоторые промежуточные возможности MongoDB: запросы предикатов, агрегатные функции и поддержка LINQ, предоставляемые сборкой MongoDB.Linq. Я также представлю некоторые замечания о размещении MongoDB в рабочей среде.

Когда мы последний раз оставили нашего героя. . .

По причинам экономии места, я не буду подробно рассматривать материал предыдущих статей; но вы можете ознакомиться с ними в Интернете в выпусках за Май и Июнь на веб-сайте msdn.microsoft.com/magazine. Однако в соответствующий пакет кода включены исследовательские тесты, которые включают существующий примерный набор данных для работы, использующие персонажи одной из моих любимых телевизионных передач. На рис. 1 показан предыдущий исследовательский тест, с целью немного освежить память. Пока все понятно.

Рис. 1 Пример исследовательского теста

[TestMethod]
        public void StoreAndCountFamilyWithOid()
        {
          var oidGen = new OidGenerator();
          var peter = new Document();
          peter["firstname"] = "Peter";
          peter["lastname"] = "Griffin";
          peter["_id"] = oidGen.Generate();

          var lois = new Document();
          lois["firstname"] = "Lois";
          lois["lastname"] = "Griffin";
          lois["_id"] = oidGen.Generate();

          peter["spouse"] = lois["_id"];
          lois["spouse"] = peter["_id"];

          var cast = new[] { peter, lois };
          var fg = db["exploretests"]["familyguy"];
          fg.Insert(cast);

          Assert.AreEqual(peter["spouse"], lois["_id"]);
          Assert.AreEqual(
            fg.FindOne(new Document().Append("_id",
              peter["spouse"])).ToString(), lois.ToString());

          Assert.AreEqual(2,
            fg.Count(new Document().Append("lastname", "Griffin")));
        }

Всем старикам. . .

В предыдущих статьях клиентский код получал все документы, соответствующие определенным критериям (например, поле «lastname» которых соответствовало определенной строке, или если поле «_id» соответствует определенному идентификатору Oid), но я не рассматривал создание запроса стиля предикатов (такие как «найти все документы, значение поля «age» которых больше 18»). Как оказалось, система MongoDB не использует интерфейс стиля SQL для описания запроса для выполнения; вместо этого она использует ECMAScript/JavaScript и может фактически принимать блоки кода для выполнения на сервере с целью фильтрации или объединения данных почти как хранимая процедура.

Это предоставляет определенные возможности наподобие LINQ, даже до рассмотрения возможностей LINQ, поддерживаемых сборкой Mongo.Linq. Путем указания документа, содержащего поля с именем «$where», и кода блока с описанием кода ECMAScript для выполнения, могут быть созданы запросы различной сложности:

 

[TestMethod]
        public void Where()
        {
          ICursor oldFolks =
            db["exploretests"]["familyguy"].Find(
            new Document().Append("$where", 
            new Code("this.gender === 'F'")));
          bool found = false;
          foreach (var d in oldFolks.Documents)
            found = true;
          Assert.IsTrue(found, "Found people");
        }

Как можно видеть, вызов Find возвращает экземпляр ICursor, который хотя и не является IEnumerable (что означает, что он не может использоваться в цикле foreach), содержит свойство Documents, являющееся IEnumerable<Document>. Если запрос возвратит большой набор данных, ICursor может быть ограничен для возврата первых nрезультатов путем установки для его свойства Limit значения n.

Синтаксис запроса предиката может иметь четыре вида, как показано на рис. 2.

Рис. 2 Четыре различных синтаксиса запроса предиката

[TestMethod]
        public void PredicateQuery()
        {
          ICursor oldFolks =
            db["exploretests"]["familyguy"].Find(
            new Document().Append("age",
            new Document().Append("$gt", 18)));
          Assert.AreEqual(6, CountDocuments(oldFolks));

          oldFolks =
            db["exploretests"]["familyguy"].Find(
            new Document().Append("$where",
            new Code("this.age > 18")));
          Assert.AreEqual(6, CountDocuments(oldFolks));

          oldFolks =
            db["exploretests"]["familyguy"].Find("this.age > 18");
          Assert.AreEqual(6, CountDocuments(oldFolks));

          oldFolks =
            db["exploretests"]["familyguy"].Find(
            new Document().Append("$where",
            new Code("function(x) { return this.age > 18; }")));
          Assert.AreEqual(6, CountDocuments(oldFolks));
        }

Во второй и третьей форме «this» всегда обозначает проверяемому объекту.

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

[TestMethod]
        public void CountGriffins()
        {
          var resultDoc = db["exploretests"].SendCommand(
            new Document()
              .Append("count", "familyguy")
              .Append("query",
                new Document().Append("lastname", "Griffin"))
            );
          Assert.AreEqual(6, (double)resultDoc["n"]);
        }

Это означает, что все статистические операции, описанные в документации MongoDB, например, «distinct» или «group», доступны через тот же механизм, даже если они не могут быть отображены так же, как методы в API MongoDB.Driver.

Базе данных можно отправлять произвольные команды вне запроса, используя синтаксис «special-name» «$eval», позволяющий выполнить на сервере любой допустимый блок кода ECMAScript, опять же, по сути, это аналогично хранимой процедуре:

[TestMethod]
        public void UseDatabaseAsCalculator()
        {
          var resultDoc = db["exploretests"].SendCommand(
            new Document()
              .Append("$eval", 
                new CodeWScope { 
                  Value = "function() { return 3 + 3; }", 
                  Scope = new Document() }));
          TestContext.WriteLine("eval returned {0}", resultDoc.ToString());
          Assert.AreEqual(6, (double)resultDoc["retval"]);
        }

Также можно использовать функцию Eval непосредственно с базой данных. Если это недостаточно гибко, MongoDB разрешает хранение определенных пользователем функций ECMAScript в экземпляре базы данных для выполнения во время выполнения запросов и серверных блоков путем добаления функций ECMAScript к специальной базе данных сбора «system.js», как описано на веб-сайте MongoDB.

Отсутствующий LINQ

Драйвер MongoDB C# также имеет поддержку LINQ, что позволяет разработчикам создавать клиентский код MongoDB, подобный показанному на рис. 3.

Рис. 3 Пример поддержки LINQ

[TestMethod]
        public void LINQQuery()
        {
          var fg = db["exploretests"]["familyguy"];
          var results = 
            from d in fg.Linq() 
            where ((string)d["lastname"]) == "Brown" 
            select d;
          bool found = false;
          foreach (var d in results)
          {
            found = true;
            TestContext.WriteLine("Found {0}", d);
          }
          Assert.IsTrue(found, "No Browns found?");
        }

Кроме того, в соответствие с динамической природой базы данных MongoDB для данного примера не требуется создание кода, необходим только вызов Linq для возврата объекта для «обеспечения» поставщика MongoDB LINQ. На момент написания этой статьи поддержка LINQ довольна минимальна, но она улучшается и на момент печати статьи будет значительно расширена. Документация для новых функций и примеры будут доступны в системе вики сайта проекта.

Доставка это будущее

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

Для начала серверный процесс (mongod.exe) должен быть установлен в качестве службы —его запуск в интерактивном сеансе рабочего стола обычно не разрешается на рабочем сервере. Для этого mongod.exe поддерживает параметр установки службы «--install» для установки в качеств службы, которая может запускаться на панели служб или через командную строку: «net start MongoDB». Однако на момент написания данной статьи команда --install имеет одну особенность, она выводит путь к исполняемому файлу с помощью командной строки, используемой для его выполнения, поэтому в командой строке должен быть указан полный путь. Это означает, что при установке MongoDB в папку C:\Prg\mongodb ее необходимо установить как службу в командной строке (с правами администратора) с помощью команды C:\Prg\mongodb\bin\mongod.exe --install.

Однако все параметры командной строки, такие как «--dbpath», также должны быть включены в командную строку, что означает, что при изменении каких-либо из этих параметров, порта, пути к файлам данным и т д., необходимо переустановить службу. К счастью, MongoDB поддерживает параметр файла конфигурации, определяемый параметром командной строки «--config», поэтому обычно наилучший подход состоит в передачи программе установки службы полного пути к файлу конфигурации и выполнению всех дополнительных настроек:

C:\Prg\mongodb\bin\mongod.exe --config C:\Prg\mongodb\bin\mongo.cfg --install
net start MongoDB

Как обычно, самый простой способ проверки успешной работы службы заключается в ее подключении к клиенту mongo.exe, который входит в состав загружаемых материалов MongoDB. И поскольку связь сервера с клиентами осуществляется через сокеты, необходимо разрешить необходимые порты в брандмауэре для разрешения связи между серверами.

Эти не те дроиды данных, которые вам необходимы

Конечно же, незащищенный доступ к серверу MongoDB не очень хорошая вещь, поэтому ключевой функций является защита сервера от нежелательных посетителей. MongoDB поддерживает проверку подлинности, but the система безопасности не настолько сложна, как система безопасности баз данных «big iron» сервера SQL Server.

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

> use admin
> db.addUser("dba", "dbapassword")

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

> db.authenticate("dba", "dbapassword")

Теперь администратор базы данных может добавлять пользователей к базе данных MongoDB путем изменения баз данных и добавления пользователей, используя показанный ранее вызов addUser:

> use mydatabase
> db.addUser("billg", "password")

При подключении к базе данных с помощью Mongo.Driver необходимо передать данные проверки подлинности как часть строки подключения, используемой для создания объекта Mongo, после чего проверка подлинности будет выполнена автоматически:

var mongo = new Mongo("123sername=billg;123assword=123assword");

Естественно, пароли не должны указываться непосредственно в коде или храниться открыто; следуйте правилам использования паролей для всех приложений баз данных. Фактически, вся конфигурация (узел, порт, пароль и т.д.) должна храниться в файле конфигурации и получаться через класс ConfigurationManager.

Стремясь достигнуть определенного кода

Время от времени администраторам будет необходимо обращаться к выполняющемуся экземпляру для получения диагностической информации о работающем сервере. MongoDB поддерживает интерфейс HTTP для взаимодействия, работающий с портом с номером на 1 больше, чем порт, настроенный для доступа для обычный связи в клиентом. Таким образом, поскольку порт MongoDB по умолчанию – 27017, интерфейс HTTP находится на порту 28017, как показано на рис. 4.

Figure 4 The HTTP Interface for Interacting with MongoDB

Рис. 4 Интерфейс HTTP для взаимодействия с MongoDB

Этот интерфейс HTTP также разрешает использование подхода связи в стиле REST, в отличие от собственного драйвера MongoDB.Driver и MongoDB.Linq; на веб-сайте MongoDB содержится подробная информация, но, в сущности, URL-адрес HTTP для доступа к содержимому набора указывается путем добавления имени базы данных и набора, разделенных косыми чертами, как показано на рис. 5.

Figure 5 The HTTP URL for Accessing a Collection’s Contents

Рис. 5 URL-адрес HTTP для доступа к содержимому набора

Подробные сведения о создании клиента REST с помощью WCF см. в статье MSDN «REST в Windows Communication Foundation (WCF)».

Сообщение от Йоды

MongoDB – это быстро развивающийся продукт, и в этих статьях, в которых рассматриваются основные функции MongoDB, остаются неосвященными большие области. Хотя MongoDB не является прямой заменой SQL Server, она проявился себя как жизнеспособная альтернатива системы хранения для областей, в которых использование традиционных РСУБД подходит меньше. Аналогично, так же как и развитие MongoDB все еще продолжается, развивается и проект mongodb-csharp.  На момент написания данной статье, множество улучшений включены в бета-версию, включая улучшения для работы со строго типизированными коллекциями с использованием обычных объектов, а также значительно улучшенную поддержку LINQ. Следит за обоими проектами.

Однако сейчас пришло время проститься с MongoDB и обратиться к другим частям мира разработчика, с которыми может быть незнаком трудящийся программист (но, пожалуй, должен). Теперь пожелаю вам приятного программирования и помните, что как сказал великий мастер Йода DevGuy: «DevGuy использует источник для знания и защиты, но никогда не для взлома».

Тэд Ньюард (Ted Neward) – глава Neward & Associates, независимой компании, специализирующейся системах корпоративных платформ Microsoft .NET Framework и Java. Он автор более 100 статей, обладатель статуса MVP по C#, является спикером INETA, а также автором и соавтором десятка книг, включая «Professional F# 2.0» (Wrox, 2010). Он регулярно занимается консультированием преподаванием. Связаться с ним можно по адресу ted@tedneward.com или через блог blogs.tedneward.com.

Выражаем благодарность следующим техническим экспертам за рецензирование статьи: Сэму Кордеру (Sam Corder) и Крейгу Уилсону (Craig Wilson)