2014-04-08 3 views
2

Mongo에서 upsert 작업을 수행하려고 할 때 Object ID 대신 ID에 대한 GUID를 생성해야합니다. 이 경우 특정 속성을 가진 객체가 아직 존재하지 않고 업데이트가 발생하면 실제로 예외를 던지는 지 확인합니다. OBJECTID으로 ID를 생성합니다 이런 식으로 이렇게MongoDB C# Upsert with Guid

// 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); 

보다는 우리가 upsert 작업을 수행하는 방법을

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

    // ... more properties and junk 
} 

그리고 여기에 있습니다 : 여기

는 클래스 정의의 스텁입니다 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가 Mongo에서 항상 이상적은 아니라는 것을 알고 있지만 다른 시스템과의 호환성 때문에 사용하고 있습니다.

답변

2

무슨 일이 일어나는지는 FindAndModify가 upsert가 될지 여부를 서버에서만 알 수 있으며 현재 작성된대로 _id 값을 자동으로 생성하는 서버이며 서버는 _id 값은 ObjectId 여야합니다 (서버는 클래스 선언에 대해 아무 것도 모릅니다). 우리는 우리가 빈 컬렉션을 실행하기 때문에 이것이 upsert 있었는지

> 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 } 
> 

:

여기에 시나리오를 보여주는 쉘을 사용하여 간단한 예 (마이너스 모든 C# 코드 ...)입니다. 서버는 새 문서 ("x"가있는 곳)의 초기 템플릿으로 쿼리를 사용하고 업데이트 사양 ("y"가 나온 위치)을 적용했으며 문서에는 "_id" 그것을 위해 새로운 ObjectId를 생성했습니다.

트릭은 필요한 경우를 대비하여 _id 클라이언트 측을 생성하는 것이지만 새 문서 인 경우에만 적용되도록 업데이트 사양에 넣는 것입니다. 여기에 _id를 위해 $ setOnInsert를 사용하여 앞의 예는 다음과 같습니다

> 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 } 
> 

이제 우리는 서버가 우리가 대신 ObjectId가 발생의 공급 _id를 사용하는 것을 알 수있다.

당신의 C# 코드의 관점에서

, 단순히 당신의 updateQuery에 다음을 추가 : 기술적으로 쿼리 아니기 때문에

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

당신은 updateSpecification (또는 갱신)로 updateQuery 변수의 이름을 변경하는 것이 좋습니다.

이 기술은 현재 2.6 버전의 서버에 대해서만 작동합니다. 참조 : https://jira.mongodb.org/browse/SERVER-9958

+0

여기에 포함 된 링크는 제공되는 정보와 함께 내 질문에 대한 답변을 제공했습니다. 설명 된 시나리오는 우리가 실행 한 것과 정확히 일치합니다. 우리는 Mongo의 어떤 버전을 설치했는지, 업그레이드 할 수 있는지 찾고 있습니다. 왜냐하면 현재 우리는 몇 가지 버전을 가지고 있기 때문입니다. – Masaka

0

당신은 이것을 위해 recommended practice을 따르는 것처럼 보이지만 어쨌든 "upserts"로 우회 될 수 있습니다. 일반적인 문제는 작업이 실제로 어떤 클래스가 실제로 처리되는지 알지 못하고 사용자 지정 ID 생성기를 호출해야한다는 것을 알 수있는 방법이 없다는 것입니다.

_id 필드에 대해 MongoDB에 전달하는 모든 값은 기본 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'명령이 실패했습니다 : 예외 : _id의 Mod이 허용되지 않았습니다 (응답 : { "errmsg": "예외 : Mod의 _id가 허용되지 않음", "code": 10148, " ok ": 0.0}). 나는 아래의 @ robert-stam의 답변이이 문제를 해결하는 데 도움이된다고 생각합니다. – Masaka

+0

@ Masaka 나는 응답이 심지어 다르지 않다고 실제로 생각한다. $ set보다는 $ setOnInsert를 사용하고 삽입시'_id'가 업데이트됩니다. 인용 된 JIRA는 사실 거짓입니다. 문제는 개발 릴리스에만 있었고 생산에는 영향을 미치지 않았습니다. 그래서 이것은 현재 버전에서 작동합니다. –