Статьи

Запрос нетипизированного объекта внутри MongoDB

Я писал об использовании MongoDb для хранения журналов log4net , но у исходного приложения есть небольшая проблема: он хранит BsonDocument внутри MongoCollection и не использует какой-либо объект C #, а когда приходит время запрашивать данные, вы не можете использовать новый LINQ. Поставщик Mongo, который включен в новейшие драйверы C # (1.4 ), потому что данные полностью нетипизированы.

Обычно это не большая проблема, потому что вы можете запросить коллекцию, используя простой JSON-подобный запрос , но если вы привыкли к стандартному поставщику LINQ , возможно, вы немного потеряете в том, как создать запрос JSON для извлечения данных. тебе нужно. Вот сценарий, который мне нужно решить : у меня есть дурацкая Winform, которая способна показать некоторую информацию из стандартной базы данных Log4Net Sql (с помощью adonetappender), и я хочу иметь возможность использовать тот же интерфейс для загрузки данных из базы данных mongo , Прежде всего мне нужно загрузить все различные значения для уровня свойства   и loggerName, потому что у меня есть комбо, где пользователь может фильтровать по серьезности (ERROR, WARN, INFO и т. д.) и список флажков, используемых для фильтрации по loggerName. К счастью, Монго предлагает такую ​​функциональность из коробки.

var db = server.GetDatabase(bsDatabase.Current as string);
var collection = db.GetCollection(bsCollections.CurrencyManager.Current as string);
var allLevel = collection.Distinct("level");

Код действительно прост: я создаю соединение с базой данных и затем извлекаю ссылку на коллекцию, которая была выбрана в интерфейсе от пользователя, затем я использую метод Distinct () объекта MongoCollection, передавая имя нужного вам свойства и Монго дал вам отдельный список каждого значения этого свойства . Это позволяет мне заполнить пользовательский интерфейс несколькими строками кода.

Теперь пришло время подумать, как создать запрос для извлечения всего документа с определенным уровнем ведения журнала и принадлежности к списку возможных loggerName . В моем программном обеспечении я обычно использую Castle Log4net Integration, это означает, что свойство loggerName равно имени класса, который выдает журнал, и обычно пользователь хочет видеть журналы, принадлежащие одному или нескольким классам, что-то вроде: все ОШИБКИ из класса или ОШИБКА . Mongo имеет вспомогательный класс QueryBuilder, который облегчает создание такого запроса с небольшой помощью intellisense и без необходимости пачкать руки напрямую с помощью JSON

var query = Query.And(
                    Query.EQ("level", "ERROR"),
                    Query.Or(
                        Query.EQ("loggerName", "classa"),
                        Query.EQ("loggerName", "classb")
                    )
                );

Теперь мне нужно сделать этот код динамическим, потому что мне нужно создать запрос, который извлекает журналы, принадлежащие неизвестному номеру loggerName , например «classa», «classb» и «classc» , потому что пользовательский интерфейс содержит CheckBoxList каждого loggerName представить в базу данных, и пользователь может выбрать любое количество элементов для поиска, поэтому мне нужно динамически создавать список условий для создания динамического запроса.

List<IMongoQuery> mainQueries = new List<IMongoQuery>();
if (!String.IsNullOrWhiteSpace(cmbLevel.Text))
{
    mainQueries.Add(Query.EQ("level", cmbLevel.Text));
}
if (cbList.CheckedIndices.Count > 0)
{
    List<IMongoQuery> listOfLoggerNameQueries = new List<IMongoQuery>();
 
    foreach (Int32 index in cbList.CheckedIndices)
    {
        String value = cbList.Items[index].ToString();
        listOfLoggerNameQueries.Add(Query.EQ("loggerName", value));
    }
    mainQueries.Add(Query.Or(listOfLoggerNameQueries.ToArray()));
}
var finalQuery = Query.And(mainQueries.ToArray());

Код действительно прост, потому что он просто создает список объекта IMongoQuery, который содержит все условия первого уровня, которые будут объединены в последней инструкции с помощником Query.And () . Поскольку я могу выбрать более одного loggerName из checkboxList, я могу просто перебрать все CheckedIndices и создать условие Query.EQ («loggerName», значение) для каждого проверенного имени в пользовательском интерфейсе, тогда я могу объединить все эти условия с Query. Или () для создания одного IMongoQuery, который добавляется в основной список .

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

var cursor = collection.Find(finalQuery);
Int32 limit;
if (!Int32.TryParse(txtLogNum.Text, out limit))
{
    limit = 50;
}
cursor.SetFields("level", "loggerName", "message", "exception", "customproperties", "timestamp");
cursor.SetSortOrder(SortBy.Descending("timestamp"));
cursor.Limit = limit;

Наконец, метод MongoCollection.Find () возвращает курсор, который на самом деле не содержит каких-либо данных, теперь вы можете добавить сортировку, разбиение на страницы и указать все свойства, которые вы хотите вернуть непосредственно в curso, и когда вы выполняете все элементы с foreach данные будут получены из базы данных. Это действительно похоже на запрос LINQ, где данные не извлекаются, если вы вызываете Where (), Select () и т. Д., Но только когда вы повторяете запрос или вызываете не отложенный оператор, такой как List ().

С помощью этого простого кода я могу создать простую форму для быстрой визуализации всех журналов, хранящихся в базе данных Mongo, даже если приложение хранит нетипизированные объекты.