MondDB C#Upsert与Guid [英] MongoDB C# Upsert with Guid

查看:78
本文介绍了MondDB C#Upsert与Guid的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当尝试在Mongo中执行向上插入操作时,我希望它为ID而不是对象ID生成GUID.在这种情况下,我要检查以确保不存在具有特定属性的对象,并且如果更新发生,实际上会引发异常.

这是一个类定义的存根:

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

解决方案

正在发生的事情是,只有服务器知道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 }
>

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

When attempting to perform an upsert operation in Mongo, I'd like to have it generate a GUID for the ID instead of an Object ID. In this case, I'm checking to make sure an object with specific properties doesn't already exist and actually throwing an exception if the update occurs.

Here's a stub of the class definition:

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

    // ... more properties and junk
}

And here is how we are performing the upsert operation:

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

Doing it this way will generate the ID as an ObjectID rather than a GUID.

I can definitely get the behavior I want as a two step operation by performing a .FindOne first, and if it fails, doing a direct insert:

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

In this case, it correctly sets the GUID for the new item, but the operation is non-atomic. I was searching for a way to set the default ID generation mechanism at the driver level, and thought this would do it:

BsonSerializer.RegisterIdGenerator(typeof(Guid), GuidGenerator.Instance);

...but to no avail, and I assume that's because for the upsert, the ID field can't be included so there is no serialization happening and Mongo is doing all of the work. I also looked into implementing a convention, but that didn't make sense since there are separate generation mechanisms to handle that. Is there a different approach I should be looking at for this and/or am I just missing something?

I do realize that GUIDs are not always ideal in Mongo, but we are exploring using them due to compatibility with another system.

解决方案

What's happening is that only the server knows whether the FindAndModify is going to end up being an upsert or not, and as currently written it is the server that is automatically generating the _id value, and the server can only assume that the _id value should be an ObjectId (the server knows nothing about your class declarations).

Here's a simplified example using the shell showing your scenario (minus all the C# code...):

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

We know this was an upsert because we ran it on an empty collection. Note that the server used the query as an initial template for the new document (that's where the "x" came from), applied the update specification (that's where the "y" came from), and because the document had no "_id" it generated a new ObjectId for it.

The trick is to generate the _id client side in case it turns out to be needed, but to put it in the update specification in such a way that it only applies if it's a new document. Here's the previous example using $setOnInsert for the _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 }
>

Now we see that the server used the _id we supplied instead of generating an ObjectId.

In terms of your C# code, simply add the following to your updateQuery:

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

You should consider renaming your updateQuery variable to updateSpecification (or just update) because technically it's not a query.

There's a catch though... this technique is only going to work against the current 2.6 version of the server. See: https://jira.mongodb.org/browse/SERVER-9958

这篇关于MondDB C#Upsert与Guid的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆