使用Guid进行MongoDB C#Upsert

时间:2014-04-08 17:03:19

标签: c# mongodb mongodb-.net-driver

当尝试在Mongo中执行upsert操作时,我想让它生成ID的GUID而不是Object ID。在这种情况下,我正在检查以确保具有特定属性的对象尚不存在,并且在更新发生时实际抛出异常。

这是类定义的存根:

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

这样做会将ID生成为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在Mongo中并不总是理想的,但由于与其他系统的兼容性,我们正在探索使用它们。

2 个答案:

答案 0 :(得分:2)

发生的事情是,只有服务器知道FindAndModify是否最终会成为upsert,并且正如当前所写,它是自动生成_id值的服务器,而服务器只能假设_id值应该是ObjectId(服务器对您的类声明一无所知)。

这是一个简单的示例,使用shell显示您的场景(减去所有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因为我们在空集合上运行它。请注意,服务器使用查询作为新文档的初始模板(&#34; x&#34;来自哪里),应用了更新规范(&#39; s所在的位置&#34; ; y&#34;来自),因为该文件没有&#34; _id&#34;它为它生成了一个新的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

答案 1 :(得分:0)

您似乎正在关注recommended practice,但可能会使用&#34; upserts&#34;不知何故。一般的问题似乎是操作实际上并不知道哪个&#34;类&#34;它实际上正在处理并且无法知道它需要调用自定义Id生成器。

您传递给MongoDB的_id字段的任何值都将被取代,而不是生成默认的ObjectID。因此,如果该字段包含在更新&#34;文档&#34;将使用的声明的一部分。

当期待&#34; upsert&#34;这可能是最安全的方法。行为是使用$setOnInsert修饰符。当从相关的&#34; upsert&#34;进行插入时,此处指定的任何内容都将设置。操作。所以一般来说:

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

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

因此 $set (或其他有效的更新运算符)中的任何内容都将始终更新&#34;当匹配&#34;查询&#34;找到条件。当&#34;插入&#34;时,将应用$setOnInsert字段。实际上由于没有匹配而发生。当然,查询部分中使用的任何文字条件都匹配&#34;也设置为未来&#34; upserts&#34;将发布&#34;更新&#34;代替。

所以只要你构建你的&#34;更新&#34; BSON文档以这种方式包含您新生成的GUID,然后您将始终获得正确的值。

您的大部分代码都在正确的轨道上,但您需要从生成器类中调用该方法,并将值放在语句的 $setOnInsert 部分中,已使用,但尚未包括_id值。