4

I have a MongoDB collection ("Users") which holds a dictionary field ("UserRegistrations").

The field definition is:

    BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)]
    public Dictionary<string, UserRegistration> UserRegistrations = new Dictionary<string, UserRegistration>();

It's a dictionary which consists of a key (String) and a customized object as value.

This is how it reflects in MongoDB:

"UserRegistrations" : [{
  "k" : "517ba4e1696b03108ccef51a",
  "v" : {
    "RegistrationDate" : ISODate("2013-07-21T18:57:42.589Z"),
    "OtherInfo" : "123456test",
  }
}],

When I use $AddToSet, as following example:

IMongoQuery query = Query.EQ("_id", new ObjectId(uid));

var kvp = new KeyValuePair<string, UserRegistration>("517ba4e1696b03108ccef51a", new UserRegistration()
{
 RegistrationDate = DateTime.Now.ToUniversalTime(),
});

IMongoUpdate update = Update.AddToSet("UserRegistrations", kvp.ToBsonDocument());

collection.Update(query, update, UpdateFlags.Multi);

It doesn't check if the same key exists already, causing a duplication in a Dictionary field which later causes deserialization errors in MongoDB C# driver.

How can I make sure the same key doesn't exists already?

Thanks!

mnnsx
  • 41
  • 5

2 Answers2

1

At the current state of your schema, i don't think it is possible to achieve what you are trying using only one update query to mongodb.

You have two options (and maybe more that i haven't thought of)

1. Edit the document on your program

Instead of just updating your document with the key-value you want to append,

  • query the database for the whole document,
  • put it into a local C# TDocument object,
  • append your key-value pair on the local copy on the document,making sure you don't add a key that already exist and
  • use the Save document method to update it into the database.

This method is really slow (compared to direct updating) but it is the only way to do what you are asking without changing your schema.

2. Change your schema

Have a separate collection (name "UserRegistrations" for ease of understanding) for each key-value pair and (i am guessing that the object which includes the array "UserRegistrations", has some other fields like UserID or something similar) create a unique index compound index of UserID and key from key-value pair.

A record in that collection would look like this:

{ 
  "UserID" : "unique_user_id_of_user_A"
  "k" : "517ba4e1696b03108ccef51a",
  "v" : {
    "RegistrationDate" : ISODate("2013-07-21T18:57:42.589Z"),
    "OtherInfo" : "123456test",
  }
}
{ 
  "UserID" : "unique_user_id_of_user_B"
  "k" : "jkfadifhafo4ho34fo78h34fo7",      #obvious keyboard spazzing here
  "v" : {
    "RegistrationDate" : ISODate("2013-07-21T19:57:42.589Z"),
    "OtherInfo" : "123456test",
  }
}
{ 
  "UserID" : "unique_user_id_of_user_A"     #same user as first record
  "k" : "sfahoihu43o43f7437843f8g348",      #different key-value pair
  "v" : {
    "RegistrationDate" : ISODate("2013-07-22T18:57:42.589Z"),
    "OtherInfo" : "123456test",
  }
}

Pro: You no longer have to worry about the 16MB limit per document and each user can have virtually limitless "UserRegistrations" (whatever they may be).

Con: You have to execute join which may be fast using index, but when using mongodb you should try to avoid them.

This is just a quick solution for your problem i came up with. If you are interested you should research how to construct your schema. MongoDb data modelling manual and this question should get you started.

Community
  • 1
  • 1
Makis Tsantekidis
  • 2,698
  • 23
  • 38
  • Hi, thanks for the answer, but this isn't what I was looking for. Separating the collection to another "UserRegistrations" collection is counter NoSQL methodology which will later require an expensive indexing. I don't need 16mb, actually about 1mb is more than enough so this isn't a pro for me. The other method of editing the document in my application is not good for me either, this method can be called asynchronously, if first call gets the current document and second call gets it too, in the meantime, the data will be conflicted. What I need is a MongoDB solution (AddToSet or similar). – mnnsx Oct 15 '13 at 17:23
0

Thanks for the answer, but this isn't what I was looking for.

Separating the collection to another "UserRegistrations" collection is counter NoSQL methodology which will later require an expensive indexing. I don't need 16mb, actually about 1mb is more than enough so this isn't a pro for me.

I need a NoSQL like o(1) direct access to the data by UID.

The second method of editing the document in my application is not good for me either, this method can be called asynchronously, if first call gets the current document and second call gets it to, in the meantime, the data will be conflicted and there will be a race condition with unknown results.

What I need is a MongoDB solution ($AddToSet or similar one line function).

After some research - currently, there is no MongoDB function that implements this.

Anyway, I ended up using a small manipulation, by simply adding a qualification to the Update statement, which first checks if a key with such ID already exists.

Answer:

IMongoQuery query = Query.And("_id", new ObjectId("RootDocumentKey")), Query.NE(string.Format("{0}.k", "FieldName"), BsonValue.Create("DictionaryKey")));

In which "RootDocumentKey", "FieldName" and "DictionaryKey" are your parameters.

Hopefully MongoDB will add this functionality soon.

mnnsx
  • 41
  • 5