MongoDB C# Upsert with Guid

本文关键字:Guid with Upsert MongoDB | 更新日期: 2023-09-27 17:57:37

当尝试在Mongo中执行upstart操作时,我希望它为ID生成一个GUID,而不是对象ID。在这种情况下,我会检查以确保具有特定属性的对象不存在,并在更新发生时实际抛出异常。

下面是类定义的存根:

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

以下是我们如何执行追加销售操作:

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

但没有用,我想这是因为对于upstart,ID字段不能被包括在内,所以没有序列化,Mongo正在做所有的工作。我也考虑过实施一个约定,但这没有意义,因为有单独的生成机制来处理这个问题。对此,我应该寻找不同的方法吗?和/或我只是错过了什么?

我确实意识到GUID在Mongo中并不总是理想的,但由于与另一个系统的兼容性,我们正在探索使用它们。

MongoDB C# Upsert with Guid

现在的情况是,只有服务器知道FindAndModify最终是否会成为一个upstart,而正如目前所写的那样,是服务器自动生成_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 }
>

我们知道这是一个意外事件,因为我们在一个空的集合上运行它。请注意,服务器使用该查询作为新文档的初始模板("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

您似乎遵循了建议的做法,但可能会以某种方式绕过"upserts"。一般的问题似乎是,操作实际上并不知道它实际处理的是哪个"类",也无法知道它需要调用自定义Id生成器。

_id字段传递给MongoDB的任何值都将始终被接受,而不是生成默认的ObjectID。因此,如果该字段包含在语句的更新"文档"部分中,则将使用该字段。

当期望"upstart"行为时,可能最安全的方法是使用$setOnInsert修饰符。当相关的"upstart"操作中发生插入时,此处指定的任何内容都将仅设置。所以一般来说:

db.collection.update(
   { "something": "matching" }
   {
       // Only on insert 
       "$setOnInsert": {
           "_id": 123
       },
       // Always applied on update
       "$set": {
           "otherField": "value"
       }
   },
   { upsert: true }
)

因此,当找到匹配的"查询"条件时,$set(或其他有效的更新运算符)中的任何内容都将始终"更新"。$setOnInsert字段将在由于不匹配而实际发生"插入"时应用。自然,查询部分中用于"匹配"的任何文字条件也会被设置,以便将来的"upserts"将发出"update"。

因此,只要你以这种方式构建你的"更新"BSON文档,以包括你新生成的GUID,那么你总是会得到正确的值。

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