当尝试在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中并不总是理想的,但由于与其他系统的兼容性,我们正在探索使用它们。
答案 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
值。