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中并不总是理想的,但由于与另一个系统的兼容性,我们正在探索使用它们。
现在的情况是,只有服务器知道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
值。