2

Consider the following class

public class Something 
{
    public ObjectId Id;
    public DateTime DbUpdatedAt;
    public string AnotherProperty;
    public int SomeIntProp;
}

I would normally do a partial update with the following code

var obj = ... // an instance of Something
var update = new UpdateBuilder<Something>();
update.Set(x => x.DbUpdatedAt, DateTime.UtcNow);
...
/// later on, 
// database is an instance of MongoDatabase
database.GetCollection("CollectionName")
        .Update(Query<Something>.Eq(x => x.Id, something.Id), update);

The problem is, I am not aware of any way to check whether update already has been configured to set a value for DbUpdatedAt. If I blindly try to set a new value for DbUpdatedAt, I get an error.

...
/// later on, 
update.Set(x => x.DbUpdatedAt, DateTime.UtcNow.AddHours(1)); // this throws a Duplicate element name error 
// database is an instance of MongoDatabase
database.GetCollection("CollectionName")
        .Update(Query<Something>.Eq(x => x.Id, something.Id), update);

I understand WHY the error happens. I need a way to,

  1. Detect there is a duplicate key scenario,
  2. Replace the old key,value pair with the new key,value pair.

2 Answers 2

2

The UpdateBuilder as currently implemented considers trying to set the same field to two different values an error (though I wish the exception thrown was more clear...).

It's not unreasonable to expect that calling Set for the same field twice would simply override the first one with the second one, but that's not how it currently works. If you feel it should work that way please file a JIRA ticket suggesting the change.

As a workaround, you could define an extension method on UpdateBuilder that behaves the way you want it to. It would probably look like this:

public static UpdateBuilder<TDocument> SetWithOverride<TDocument, TField>(this UpdateBuilder<TDocument> update, Expression<Func<TDocument, TField>> memberExpression, TField value)
{
    var set = Update<TDocument>.Set(memberExpression, value).ToBsonDocument();
    var combined = update.ToBsonDocument();
    if (combined.Contains("$set"))
    {
        var element = set[0].AsBsonDocument.GetElement(0);
        combined["$set"][element.Name] = element.Value;
    }
    else
    {
        combined.Merge(set);
    }
    return Update<TDocument>.Combine(combined.Elements.Select(e => new UpdateDocument(e)));
}

And here's some sample code showing it being used:

var update = Update<C>.Set(c => c.X, 1).Set(c => c.Y, 2);
update = update.SetWithOverride(c => c.Y, 3);
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks. That worked well. For this to work in all cases, I will always have to use SetWithOverride (as I am never sure if another function has already called set or not). With that in mind, does this function have any performance issues? The code converts the two update statements to bson documents and then back to an update statement. Does all that deserialization/serialization have an impact which could accumulate over many calls?
Also, I created a JIRA issue (jira.mongodb.org/browse/CSHARP-902). Hope, it will be implemented soon :)
0

From the manuals:

Use the $set operator to replace the value of a field to the specified value. If the field does not exist, the $set operator will add the field with the specified value.

From your description, you shouldn't be getting any duplicate key errors, unless you've made DbUpdatedAt part of some unique index or something.

I think a more common mistake would be to insert a document rather than to update one (which is easy to do using the C# driver) but that won't be happening if you're doing an update using Mongo's query builders, as you appear to be doing.

All in all, the error you're getting doesn't make sense. Are you sure there's not something else going on that is causing this error?

1 Comment

Thanks for the answer. However, the issue I am facing is different. The way the code is written, it results in the following document: {$set: { 'DbUpdated': <some date value>, 'DbUpdated': <another date value> }}... The document has the same key DbUpdated twice, which is invalid. @RobertStam has suggested a workaround which checks whether the document already contains the key and handles it accordingly.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.