Pull to refresh

MongoDB и C# driver от 10gen, неочевидные моменты

Reading time4 min
Views5.5K

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

После первого поста про mongodb я на несколько недель переключался на f# и вернувшись понял, что всё забыто. Пришлось перечитывать собственный пост, и видимо не одному приходится разбирать монго, раз даже Dmitry Nesteruk влепил мне like. Лирическое отступление закончилось. Сразу оговорюсь, в данной статье как и в предыдущей речь пойдёт о драйвере, ссылка на который размещена на сайте монго, это драйвер от 10gen.

Частичная загрузка объектов


Начав переводить один из реальных проектов на Монго я сразу столкнулся с необходимостью загружать объекты без вложенных коллекций, которые он содержит. Надо заметить, что рассматриваемый драйвер работает очень просто. Через классы хелперов и визардов он просто складывает строки чтобы получить работающий запрос на родном для mongo javascript. Чтобы не загружать какое то поле, достаточно в запросе указать field_name:0

  db.users.find({}, {thumbnail:0});<br/>

В драйвере всё не так очевидно, но после некоторого копания удалось получить вот такой код:

MongoCollection coll = GetCollection();<br/>
FieldsBuilder fbExclude = Fields.Exclude(new string[]{“thumbnail”});<br/>
//can be FindAllAs<TEntity>()
MongoCursor result = coll.FindAll().SetFields(fbExclude);<br/>

Все результаты в курсоре будут без поля thumbnail, проверено, работает замечательно. Все не загруженные поля будут инициализированы значениями по умолчанию, для коллекций null. Тут есть одна тонкость, если вы не загрузите какое либо поле, а потом запишете этот объект, то он совершенно законно перетрёт старый в базе и вот такой тест упадёт на последнем Assert-е:

public class Data<br/>
{<br/>
  [BsonId]<br/>
  public ObjectId Id {get;set;}<br/>
  public int Area {get;set;}<br/>
}<br/>
Data x = new Data();<br/>
x.Area = 20;<br/>
var db = GetDb();<br/>
var coll = db.GetCollection<Data>(typeof(Data).FullName);<br/>
db.ClearColl<Data>();<br/>
coll.Save(x);<br/>
Data y = coll.FindAllAs<Data>().SetFields(Fields.Exclude(new string[]{"Area"})).FirstOrDefault();<br/>
Assert.AreEqual(0, y.Area);<br/>
coll.Save(y);<br/>
Data z = coll.FindAllAs<Data>().SetFields(
Fields.Exclude(new string[] { "Area" })
).FirstOrDefault();<br/>
//fail of course
Assert.AreEqual(20, z.Area);<br/>

Я думаю это как то решается через Find and modify, но пока такой потребности не стоит и всё что нужно менять грузится полностью.

Выбор по элементу вложенного массива


В моём случае была задача выбрать регион на основе почтового кода одной европейской страны. Т.к. по историческим причинам общая логика работала не во всех случаях, то регион должен был определятся по следующему алгоритму: если есть точное совпадение, то выбираем найденный регион, если совпадения нет, то оставляем первые 2 цифры и делаем предположение о регионе на основе диапазона почтовых кодов в регионе, причём самих диапазонов может быть много. Объект выглядит как то так:

public class HighLevelCodeInterval <br/>
{<br/>
  public HighLevelCodeInterval() { }<br/>
    <br/>
  public HighLevelCodeInterval(int mn, int mx) <br/>
  {<br/>
    Min = mn; Max = mx;<br/>
  }<br/>
  public int Min { get; set; }<br/>
  public int Max { get; set; }<br/>
}<br/>
public class RegionObject <br/>
{<br/>
  public RegionObject() <br/>
  {<br/>
    PostalCodes = new List<int>();<br/>
  }<br/>
 <br/>
  [BsonId]<br/>
  public int Id { get; set; }<br/>
  public string Name { get; set; }<br/>
  public List<HighLevelCodeInterval> HighLevelCodes { get; set; }<br/>
  public List<int> PostalCodes { get; set; }<br/>
}<br/>

Тогда запрос выборки по конкретному коду будет:

QueryComplete q = Query.EQ("PostalCodes", postCode);<br/>
MongoCollection coll = GetCollection();<br/>
RegionObject result = coll.FindOneAs<RegionObject>();<br/>

Я бы сказал неожиданно просто. Для поиска по диапазонам приведу только сам запрос:

int nCodeValue = …;<br/>
Query.And(     <br/>
       Query.LTE("HighLevelCodes.Min", nCodeValue),<br/>
                  Query.GTE("HighLevelCodes.Max", nCodeValue)<br/>
)<br/>

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

Идентификаторы


Драйвер поддерживает 2 встроенных генератора идентификаторов: для Guid и ObjectId типов, т.е. достаточно декорировать свойства одного из этих типов аттрибутом BsonId и дальше всё сделается само. Но идентификатор может быть любым, у меня отлично работают целочисленные идентификаторы, но за их уникальностью следит внешняя по отношению к монго программа. Так же утверждают, есть возможность самом написать генератор:

public class EmployeeIdGenerator : IIdGenerator <br/>
{<br/>
  object GenerateId(){ ⋮ }<br/>
  bool IsEmpty(object id) { ⋮ }<br/>
}<br/>
public class Employee <br/>
{ <br/>
  [BsonId(IdGenerator = typeof(EmployeeIdGenerator)]<br/>
  public int Id { get; set; }<br/>
  // other fields or properties
}<br/>

Сам я правда не пробовал.

Заключение


Документо ориентированные базы данных подходят далеко не для всех проектов. Если не получается красиво, то может просто DDD неприменим. Я нашёл что один из моих проектов идеально укладывается в DDD и несказанно рад исчезновению огромного числа таблиц, если всё будет хорошо, то скоро будут тесты производительности в духе MondoDB vs MSSQL на реально работающем приложении.

PS: Хочу упомянуть утилиту, которая очень помогает посмотреть что записалось в базу в результате выполненных операций – это Mongo Vue
Tags:
Hubs:
Total votes 36: ↑27 and ↓9+18
Comments20

Articles