Azure-cosmos-dotnet-v2: Update Document in-place doesn't work as expected?

Created on 5 Apr 2016  路  21Comments  路  Source: Azure/azure-cosmos-dotnet-v2

So my code looks like this:

public class CustomData: Document {
      DateTimeEpoch lastAccessed;
};

CustomData doc = client.CreateDocumentQuery<CustomData>(this.collection.SelfLink).Where(id == "queryid").AsEnumerable().First();
doc.lastAccessed = new DateTimeEpoch(DateTime.UtcNow); // <--- dynamic view isn't updated properly here (1)
this.client.ReplaceDocumentAsyc(doc).Wait();

This code fails to update the timestamp. However, change the code to the following will make it work.

CustomData doc = client.CreateDocumentQuery<CustomData>(this.collection.SelfLink).Where(id == "queryid").AsEnumerable().First();
CustomData updated = (dynamic)doc;
updated.lastAccessed = new DateTimeEpoch(DateTime.UtcNow);
this.client.ReplaceDocumentAsyc(doc.SelfLink, updated).Wait();

Stepping through the code, it seems that the dynamic view isn't updated in the first snippet at (1).
Is this expected? If so, can you please explain and make sure it's documented?

backlog bug

Most helpful comment

@smartnose @tomeglenn: There seems to be a bug wherein if you extend your class from Document and try inserting that object into DocumentDB, none of the properties get persisted. So I'd avoid extending from Document class. You could extend from Resource in this case like:

public class CustomData: Resource {
DateTimeEpoch lastAccessed;
};

In that case, you cannot use ReplaceDocumentAsync method that takes in Document type parameter but instead use the one that takes SelfLink and the updated object like:

CustomData doc = client.CreateDocumentQuery(this.collection.SelfLink).Where(doc => doc.id == "queryid").AsEnumerable().First();
doc.lastAccessed = new DateTimeEpoch(DateTime.UtcNow);
this.client.ReplaceDocumentAsyc(doc.SelfLink, doc).Wait();

That should work for you. Let me know if you have any more queries.

All 21 comments

I'm having the exact same issue. I thought I was going mad!

For full details: http://stackoverflow.com/questions/38324047/replacing-document-in-documentdb-not-saving-changes

@rnagpal Can we get an update on this? This was opened back in April and it's a pretty fundamental issue... Thanks.

@tomeglenn I'll take a look at it tomorrow.

@rnagpal Much appreciated, thank you :+1:

@smartnose @tomeglenn: There seems to be a bug wherein if you extend your class from Document and try inserting that object into DocumentDB, none of the properties get persisted. So I'd avoid extending from Document class. You could extend from Resource in this case like:

public class CustomData: Resource {
DateTimeEpoch lastAccessed;
};

In that case, you cannot use ReplaceDocumentAsync method that takes in Document type parameter but instead use the one that takes SelfLink and the updated object like:

CustomData doc = client.CreateDocumentQuery(this.collection.SelfLink).Where(doc => doc.id == "queryid").AsEnumerable().First();
doc.lastAccessed = new DateTimeEpoch(DateTime.UtcNow);
this.client.ReplaceDocumentAsyc(doc.SelfLink, doc).Wait();

That should work for you. Let me know if you have any more queries.

@smartnose @tomeglenn : I'm updating the ReplaceDocumentAsync API in 1.9.1 that can also take the "named" link as the first parameter, so that you don't have to query the doc just to get the selflink to be passed to this method.

CustomData data = new Customdata();
// Set whatever properties you need to set

client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(databaseName, collectionName), data ).Wait();

data.lastAccessed = new DateTimeEpoch(DateTime.UtcNow);
client.ReplaceDocumentAsyc(UriFactory.CreateDocumentUri(databaseName, collectionName, data.Id), data).Wait();

Remember to have CustomData extend from Resource instead of Document.

Please use the recommendation in the above post to resolve this. I'm closing this issue.

@rnagpal This isn't a fix to the problem which you have admitted is a bug, it's a workaround and a hacky one at that. I would like to extend my objects from Document not Resource, and since ReplaceDocumentAsync takes a Document, not a Resource, your solution is not really an acceptable one...

For starters it requires you to have access to the DatabaseName and CollectionName values at the time you wish to save changes to your document, which is not always a reasonable assumption. Especially if you're trying to abstract all of that out as I am with my library Documental (https://github.com/tomeglenn/documental).

I recently ran into this issue was a little disappointed with the fix suggested by @rnagpal. It seems the ReplaceDocumentAsyc has a bug. Any chance we will see the actual method fixed? Seems like it should be able to update a class that extends document, not just a resource.

Having the same issue... Any updates on this? @rnagpal

What is a proper work around for this? I want to extend Document. It just makes sense. I am assuming that it's some sort of caching issue where the Timestamp of the Document isn't updating properly when you set a property on the document. I would invalidate the Timestamp except Azure.Document hides it.

This is a quite frustrating bug and really degrades the Azure DocumentDB experience.

And... why is this marked as Closed? Shouldn't you have marked it as "Too lazy to fix"?

Reactivating it and adding to the backlog.

So two years later there's no fix? Bugs like this only discourage people from using DocumentDB as they can't rely on the data and may not have found this page. This should be a high priority fix.

When you inherit from Resource and UpsertDocumentAsync if the document has a geographic point in it then the upsert corrupts the data:

Good location:
"location": {
"type": "Point",
"coordinates": [
-86.081,
39.606
]
},

After upsert:
"location": {
"$id": "2",
"coordinates": [
-86.081,
39.606
]
},

This also stops me from being able to convert the result.Resource to my model using:

(T)(dynamic)result.Resource

Because the geographic point is corrupted attempting to cast causes a NullReferenceException:
Data: {System.Collections.ListDictionaryInternal}
HResult: -2147467261
HelpLink: null
InnerException: null
Message: "Object reference not set to an instance of an object."
Source: "Microsoft.Azure.Documents.Client"
StackTrace: " at Microsoft.Azure.Documents.Spatial.Converters.GeometryJsonConverter.ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.DeserializeConvertable(JsonConverter converter, JsonReader reader, Type objectType, Object existingValue)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.SetPropertyValue(JsonProperty property, JsonConverter propertyConverter, JsonContainerContract containerContract, JsonProperty containerProperty, JsonReader reader, Object target)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.PopulateObject(Object newObject, JsonReader reader, JsonObjectContract contract, JsonProperty member, String id)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object
existingValue)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader reader, Type objectType, JsonContract contract, JsonProperty member, JsonContainerContract containerContract, JsonProperty containerMember, Object existingValue)\r\n at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)\r\n at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)\r\n at Newtonsoft.Json.Linq.JToken.ToObject(Type objectType, JsonSerializer jsonSerializer)\r\n at Newtonsoft.Json.Linq.JToken.ToObjectT\r\n at Microsoft.Azure.Documents.Document.AsTypeT\r\n at System.Dynamic.UpdateDelegates.UpdateAndExecute1T0,TRet"
TargetSite: {System.Object ReadJson(Newtonsoft.Json.JsonReader, System.Type, System.Object, Newtonsoft.Json.JsonSerializer)}

In our case, document.Property = newValue stopped working as soon as we added a collection to the model that inherits from the Document class. The 'fix' we are currently using is the following:

document.SetPropertyValue(nameof(document.Property), newValue)

This does change the property in cosmos.

Here we are, years later, and this is still unresolved, costing developers time and money.

@biggyspender what version of the SDK are you seeing this? This is not an issue in the v3 SDK.

@j82w Thanks for the headsup. We're using Microsoft.Azure.DocumentDB v2.4.0. It's time to upgrade, of course. I'll get back here if problem persists.

Too bad v3 isn't released yet

Also note in v2 it didn't happen ALL the time, just when you were least expecting it. What we ended up having to do is inherit from document but every property would internally so a SetPropertyValue and set a private member variable.

@RobinHSanner why do you think v3 isn't released yet? It's GA. The nuget did change to reflect the name changes to the service: https://www.nuget.org/packages/Microsoft.Azure.Cosmos/

Hmmm, didn't notice it went. I'm still using rc1 and was waiting for the release. Thank you

Was this page helpful?
0 / 5 - 0 ratings