Upserting in Mongo DB and the Id problem

8,861 views
Skip to first unread message

YuriyHohan

unread,
Sep 2, 2011, 8:41:55 AM9/2/11
to mongodb-csharp
I have a problem while upserting to mongo db using the official C#
driver.

public abstract class AggregateRoot
{
/// <summary>
/// All mongoDb documents must have an id, we specify it here
/// </summary>
protected AggregateRoot()
{
Id = ObjectId.GenerateNewId();
}

[BsonId]
public ObjectId Id { get; set; }
}
My entities already have the id-s but I had to create the mongo
specific Id for it to work, as all the documents in a collection
should have one. Now then I receive a new entity in my system a new
Mongo Id is generated and I get the mongo cannot change _id of a
document old exception. Is there some work-around?

Robert Stam

unread,
Sep 2, 2011, 9:09:43 AM9/2/11
to mongodb-csharp
You don't need to assign a value to the Id in your constructor. If
it's a new document, the driver can generate the value for you when
you call Insert. If it's an existing document, the value will be set
to the value read from the database.

Having said that, I don't think that's the cause of your problem. I
assume you got the error when you called Update? Can you provide the
exact wording of the error message, a stack trace, and a snippet of
your code where you call the C# driver?

YuriyHohan

unread,
Sep 2, 2011, 9:14:36 AM9/2/11
to mongodb-csharp
No I have not got any error in my program. Only on the level on
MongoDB server and no update of a document in the DB as a result.

Robert Stam

unread,
Sep 2, 2011, 9:48:31 AM9/2/11
to mongodb-csharp
I was hoping you would provide some more details so that I could see
what you were doing.

The error message you are getting occurs when your code attempts to
change the value of an _id of an existing document in the database,
which is not allowed.

I can think of at least two ways this might be happening:

1. Explicitly changing the value with Set:

var query = Query.EQ("_id", oldValue);
var update = Update.Set("_id", newValue);
var result = collection.Update(query, update);

2. Implicitly changing the _id value while updating the entire
document:

var document = ...; // fetched from outside MongoDB and assigned a
new random _id
var query = ...; // some query that doesn't involve _id
var update = Update.Replace(document);
var result = collection.Update(query, update, UpdateFlags.Upsert);

In this second case you would be attempting to replace an entire
document with a new one that has a different _id value.

Does either of these cases match your code?

YuriyHohan

unread,
Sep 2, 2011, 10:01:00 AM9/2/11
to mongodb-csharp
Yes, my problem corresponds to the second case that you described. I
can provide more info of course, just do not know what to provide

Robert Stam

unread,
Sep 2, 2011, 10:12:59 AM9/2/11
to mongodb-csharp
You need to find a way to update the document in MongoDB without
assigning a new value to the _id.

One option would be to find out if the record exists first and what
its _id value is. It could look something like:

var document = ...;// fetched from outside MongoDB and assigned a
new random _id
var query = ...; // some query that doesn't involve _id
var existing =
collection.FindAs<BsonDocument>(query).SetFields("_id").SetLimit(1).FirstOrDefault();
if (existing != null) {
document.Id = existing["_id"].AsObjectId;
}
var update = Update.Replace(document);
var result = collection.Update(query, update, UpdateFlags.Upsert);

Another option would be to use the primary key from you outside source
as the _id value. Then you wouldn't have to look it up. The _id value
doesn't have to be an ObjectId, it just has to be unique. It can be
any data type, even a compound key represented as an embedded document
if necessary.

A third option, if you know which fields changed value, is to use a
series of Update.Set modifiers to change individual field values
(leaving the _id unchanged of course).

YuriyHohan

unread,
Sep 2, 2011, 10:32:09 AM9/2/11
to mongodb-csharp
Let me describe the design a bit. All the entities which would be
stored as documents were inheriting from AggregateRoot which had the
id generation in it. Every sub-document had its id generated
automatically and I had no problem with this. The id in AggregateRoot
was introduced to correct the problem when retrieving data from
MongoCollection to List and the generation was introduced so the id-s
are different. Now we can move that id generation to save methods
because the new entity for update had a new id generation. But it
would mean that every dev on the team must not forget generating id-s
in repository which is risky. It would be nicer just to ignore the id
than mapping from mongo if it is possible and not to have
AggregateRoot class at all

Robert Stam

unread,
Sep 2, 2011, 10:40:09 AM9/2/11
to mongodb-csharp
Every document in a MongoDB collection must have an _id. There is no
way around that.

You don't have to generate the _id value yourself for new documents,
the driver automatically generates an _id value if you haven't
supplied one.

But you do have to deal with the Update problem you are encountering.
You simply are not allowed to change the value of the _id.

I don't know what you mean when you say: "the problem when retrieving
from MongoCollection to List"... what problem were you having?

YuriyHohan

unread,
Sep 2, 2011, 10:45:32 AM9/2/11
to mongodb-csharp
I was having a problem when it tried to map a document from
MongoCollection to my entity and couldn't find the corresponding
property for the _id because there was none in there

Robert Stam

unread,
Sep 2, 2011, 11:04:08 AM9/2/11
to mongodb-csharp
OK. True, since every document in MongoDB has an _id element your
class must have a matching property or field (usually called "Id" in
the C# code).

YuriyHohan

unread,
Sep 2, 2011, 11:21:47 AM9/2/11
to mongodb-csharp
Can I make it ignore mapping the id property?

Robert Stam

unread,
Sep 2, 2011, 11:52:55 AM9/2/11
to mongodb-csharp
Maybe... you could try remove the Id property and using the
[BsonIgnoreExtraElements] attribute on your AggregateRoot class. But
that means ignoring *all* extra elements, not just the _id.

Since the _id value is such a central concept of a document in
MongoDB, I would think you would be much better off figuring out how
to work with it than trying to ignore it.

Юрий Хохан

unread,
Sep 5, 2011, 10:42:50 AM9/5/11
to mongodb...@googlegroups.com
I am still not sure about the best way to solve the problem but I made a sample app with a problem in it. How would you advise to re-write it?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;
using MongoDB.Driver.Builders;

namespace ConsoleApplication2
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Test();
        }

        private static void Test()
        {
            var _mongoServer = MongoServer.Create(
                @"mongodb://localhost/test");

            var database = _mongoServer.GetDatabase("Sample");
            var collection = database.GetCollection("guids");

            var guid = new Guid("71112225-3212-1313-3131-113322223333");
            var item = new Item
                           {
                               Id = ObjectId.GenerateNewId().ToString(),
                               GuidField = guid,
                               Name = "C"
                           };
            collection.Update(
                Query.EQ("GuidField", item.GuidField),
                Update.Replace(item),
                UpdateFlags.Upsert);
            collection.Insert(item);
            var itemFromDb = collection.Find(Query.EQ("GuidField", guid)).ToList();
        }

        public class Item
        {
            [BsonId]
            public string Id { get; set; }

            public Guid GuidField { get; set; }

            public string Name { get; set; }
        }
    }

}


2011/9/2 Robert Stam <rstam...@gmail.com>

Robert Stam

unread,
Sep 5, 2011, 2:02:41 PM9/5/11
to mongodb-csharp
I don't have any new suggestions other than the three provided
earlier:

1. query to find the current _id before using Update.Replace (see
sample code provided earlier)
2. use the primary key from your external source as the value of the
_id
3. use a series of Update.Set (one for each property) instead of
Update.Replace

My recommendation would be option 2. In your sample app that would
mean removing the GuidField property and setting Id to the value of
the Guid.
> 2011/9/2 Robert Stam <rstam10...@gmail.com>

Daniel Harman

unread,
Sep 6, 2011, 1:21:42 PM9/6/11
to mongodb...@googlegroups.com
You seem to be trying to maintain 2 keys. The _id field can contain a guid as a string. So suggest you put it there. 

Sent from my iPhone

Renat Khayretdinov

unread,
Nov 30, 2012, 10:32:52 AM11/30/12
to mongodb...@googlegroups.com
YuriyHohan, you can remove generation of ObjectId, make Id property of type ObjectId and mark this property by attribute BsonIgnoreIfDefault. 
In this case driver won't include empty Id field into update statement and mongodb server will generate ObjectId for this field if it necessary.

понедельник, 5 сентября 2011 г., 20:42:50 UTC+6 пользователь YuriyHohan написал:
Reply all
Reply to author
Forward
0 new messages