2014-04-08 3 views
2

При попытке выполнить операцию upsert в Mongo, я хотел бы, чтобы он генерировал идентификатор GUID для идентификатора вместо идентификатора объекта. В этом случае я проверяю, чтобы объект со специфическими свойствами еще не существовал и фактически выдавал исключение, если обновление происходит.MongoDB C# Upsert with Guid

Вот заглушки из определения класса:

public class Event 
{ 
    [BsonId(IdGenerator = typeof(GuidGenerator))] 
    [BsonRepresentation(BsonType.String)] 
    [BsonIgnoreIfDefault] 
    public Guid Id { get; set; } 

    // ... more properties and junk 
} 

А вот как мы выполняем операцию upsert:

// query to see if there are any pending operations 
var keyMatchQuery = Query<Event>.In(r => r.Key, keyList); 
var statusMatchQuery = Query<Event>.EQ(r => r.Status, "pending"); 
var query = Query.And(keyMatchQuery , statusMatchQuery); 

var updateQuery = new UpdateBuilder(); 
var bson = request.ToBsonDocument(); 

foreach (var item in bson) 
{ 
    updateQuery.SetOnInsert(item.Name, item.Value); 
} 

var fields = Fields<Request>.Include(req => req.Id); 

var args = new FindAndModifyArgs() 
{ 
    Fields = fields, 
    Query = query, 
    Update = updateQuery, 
    Upsert = true, 
    VersionReturned = FindAndModifyDocumentVersion.Modified 
}; 

// Perform the upsert 
var result = Collection.FindAndModify(args); 

делая это таким образом будет генерировать идентификатор в качестве ObjectID, а не GUID.

Я могу определенно получить поведение я хочу, как операции в два этапа, выполняя .FindOne первый, и если это не удается, делая прямую вставку:

var existingItem = Collection.FindOneAs<Event>(query); 

if (existingItem != null) 
{ 
    throw new PendingException(string.Format("Event already pending: id={0}", existingItem.Id)); 
} 

var result = Collection.Insert(mongoRequest); 

В этом случае, он правильно устанавливает GUID для нового элемента, но операция неатомная. Я искал способ, чтобы установить механизм по генерации ID по умолчанию на уровне драйвера, и думал, что это будет сделать это:

BsonSerializer.RegisterIdGenerator(typeof(Guid), GuidGenerator.Instance); 

... но безрезультатно, и я предполагаю, что это потому, что для upsert , поле ID не может быть включено, поэтому сериализация не происходит, и Mongo выполняет всю работу. Я также изучил реализацию конвенции, но это не имело смысла, поскольку для этого есть отдельные механизмы генерации. Есть ли другой подход, на который я должен смотреть, и/или я просто что-то пропустил?

Я действительно понимаю, что GUID не всегда идеальны в Монго, но мы изучаем их использование из-за совместимости с другой системой.

ответ

2

Происходит то, что только сервер знает, будет ли FindAndModify в конечном итоге быть upsert или нет, и в настоящее время он является сервером, который автоматически генерирует значение _id, и сервер может только предположить, что Значение _id должно быть ObjectId (сервер ничего не знает о ваших объявлениях классов).

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

> db.test.drop() 
> db.test.find() 
> var query = { x : 1 } 
> var update = { $setOnInsert : { y : 2 } } 
> db.test.findAndModify({ query: query, update : update, new : true, upsert : true }) 
{ "_id" : ObjectId("5346c3e8a8f26cfae50837d6"), "x" : 1, "y" : 2 } 
> db.test.find() 
{ "_id" : ObjectId("5346c3e8a8f26cfae50837d6"), "x" : 1, "y" : 2 } 
> 

Мы знаем, что это было upsert, потому что мы провели его на пустой коллекции. Обратите внимание, что сервер использовал запрос в качестве исходного шаблона для нового документа (откуда появился «x»), применил спецификацию обновления (откуда и пришел «y»), а также потому, что в документе не было «_id», он создал для него новый ObjectId.

Фокус в том, чтобы сгенерировать клиентскую сторону _id на случай, если она окажется необходимой, но поместить ее в спецификацию обновления таким образом, чтобы она применима только в том случае, если это новый документ. Вот предыдущий пример, используя $ setOnInsert для _id:

> db.test.drop() 
> db.test.find() 
> var query = { x : 1 } 
> var update = { $setOnInsert : { _id : "E3650127-9B23-4209-9053-1CD989AE62B9", y : 2 } } 
> db.test.findAndModify({ query: query, update : update, new : true, upsert : true }) 
{ "_id" : "E3650127-9B23-4209-9053-1CD989AE62B9", "x" : 1, "y" : 2 } 
> db.test.find() 
{ "_id" : "E3650127-9B23-4209-9053-1CD989AE62B9", "x" : 1, "y" : 2 } 
> 

Теперь мы видим, что сервер использовал _id мы поставили вместо генерации ObjectId.

С точки зрения вашей C# код, просто добавьте следующие строки в updateQuery:

updateQuery.SetOnInsert("_id", Guid.NewGuid().ToString()); 

Вы должны рассмотреть вопрос о переименовании переменную updateQuery в updateSpecification (или просто обновление), потому что технически это не запрос.

Есть уловка, хотя ... эта техника будет работать только против текущей версии 2.6 сервера. См.: https://jira.mongodb.org/browse/SERVER-9958

+0

Введенная здесь ссылка помогла ответить на мой вопрос в сочетании с предоставленной информацией. Описанный сценарий был именно тем, с чем мы столкнулись. Мы изучаем, какую версию Монго мы установили, и можем ли мы ее обновить, потому что в настоящее время у нас есть несколько версий. – Masaka

0

Для этого вам, похоже, следует recommended practice, но, возможно, это как-то обходит «upserts». Общая проблема заключается в том, что операция фактически не знает, к какому «классу» она фактически имеет дело, и не знает, что ей нужно вызвать пользовательский генератор Id.

Любое значение, которое вы передаете MongoDB для поля _id, всегда будет выполняться вместо создания ObjectID по умолчанию. Поэтому, если это поле включено в часть «документа» для обновления, это будет использоваться.

Вероятно, самый безопасный способ сделать это, ожидая, что поведение «upsert» - использовать модификатор $setOnInsert. Все, что указано здесь, будет установлено только , если вставка происходит из соответствующей операции «upsert». Таким образом, в общих чертах:

db.collection.update(
    { "something": "matching" } 
    { 
     // Only on insert 
     "$setOnInsert": { 
      "_id": 123 
     }, 

     // Always applied on update 
     "$set": { 
      "otherField": "value" 
     } 
    }, 
    { upsert: true } 
) 

Так что ничего в пределах $set (или другие действующие операторы обновления) всегда будет «обновляться», когда соответствующий «запрос» условие найдено. Поля $setOnInsert будут применяться, когда «вставка» на самом деле происходит из-за отсутствия соответствия. Естественно, любые литеральные условия, используемые в части запроса для «соответствия», также устанавливаются таким образом, чтобы в будущем «upserts» выдавал «обновление».

До тех пор, пока вы структурируете свой «обновленный» документ BSON, чтобы включить свой вновь созданный GUID таким образом, вы всегда получите правильное значение.

Большая часть кода находится на правильном пути, но вам нужно будет вызвать метод из вашего генератора класса и место значения в части заявления, которое вы уже используете $setOnInsert, но только что не включая _id значение еще.

+0

Так что я действительно пробовал это, и я должен был включить его в свой первоначальный вопрос. Когда я попробовал это сначала, я получил эту ошибку: Ошибка команды findAndModify: исключение: Mod on _id не разрешено (ответ: {"errmsg": "exception: Mod on _id not allowed", "code": 10148, " ok ": 0.0}). Я считаю, что ответ от @ robert-stam ниже помогает решить эту проблему. – Masaka

+0

@ Масака Я действительно верю, что ответ даже не отличается. Вы используете $ setOnInsert, а не $ set, а '_id 'обновляется во вставке. Процитированная JIRA на самом деле неверна. Вопрос был только в выпуске разработки и никогда не влиял на производство. Так что это должно работать в вашей текущей версии. –