4
votes

I'm using the DocumentDB API of Cosmos DB in Azure. Documents are taken from an EventHub and stored to the database by an Azure function triggered by that EventHub.

The documents I'm saving also contain a couple of enums. Unfortunately these enum values are serialized using their index number instead of enum values (see here: https://i.stack.imgur.com/3nP9o.png). Or, to be more precise, this is what happens when the function is running in Azure. Tests in a local IDE are working fine.

This is the code that is writing to Cosmos/DocumentDB:

DocumentClient = new DocumentClient(new Uri(dbServiceEndpoint), dbAuthKey);
...
var response =  DocumentClient.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), tick);
response.Wait();

"tick" is the mentioned object to be stored.

I tried a couple of things to make the document client serialize enums correctly:

  • Annotation at enum property:

    [Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))]
    public OrderActionEnum? Action { get; set; }
    
  • Annotation at enum defintion:

    [Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))]
    public enum OrderActionEnum
    {
        NEW,
        CHANGE,
        DELETE,
        SNAPSHOT,
        INITIAL
    }
    
  • Passing converter to constructor of document db client:

    DocumentClient = new DocumentClient(new Uri(dbServiceEndpoint), dbAuthKey,                
        new JsonSerializerSettings
        {
            Converters = new List<JsonConverter> {new StringEnumConverter()}
        });
    
  • Setting a default serializer:

    JsonConvert.DefaultSettings = () => new JsonSerializerSettings
    {
       Converters = new List<JsonConverter> {new StringEnumConverter()}
    };
    

None of these tries convinced the Cosmos/DocumentDB to store the enum values.

One interesting effect was that the third variant (passing converter to constructor of DocumentClient) resulted in an exception when running Azure (local tests worked fine):

2017-11-24T15:08:55.291 System.MissingMethodException : Method not found: 'Void Microsoft.Azure.Documents.Client.DocumentClient..ctor(System.Uri, System.String, Newtonsoft.Json.JsonSerializerSettings, Microsoft.Azure.Documents.Client.ConnectionPolicy, System.Nullable`1<Microsoft.Azure.Documents.ConsistencyLevel>)'.
   at TickAnalysis.DataManager.Persistency.AbstractCosmosClient`1..ctor(ILogger logger,String dbServiceEndpoint,String dbAuthKey,String databaseId,String collectionId)
   at TickAnalysis.DataManager.DataManagerFunction.RunStoreOrderEvent(String message,TraceWriter log)

This looked a bit like different versions of the package Microsoft.Azure.DocumentDB are being used. So I compared the DLLs in my local bin with the one deployed for the function in Azure. In both cases it was version 1.19.1.

Now I'm running out of ideas. I could replace the enums by plain string, but I really don't want to do that. Any hints are appreciated.

I'm aware of similar questions being asked before. For example:

Unfortunately none of these solved my problem.

2
Just want to say I appreciate you being detailed in your troubleshooting. Your "Passing converter to constructor of document db client" step helped me with an issue I was having. My client was doing the opposite. Not deserializing the enums that were stored as strings properly. So, +1 for you!Allen Rufolo

2 Answers

2
votes

I found a workaround that solved my problem:

  • Serialize the object that is supposed to be saved into a json string...
  • ...then deserialize it into a generic JObject...
  • ...and then save that.

Code looks like this:

DocumentClient = new DocumentClient(new Uri(dbServiceEndpoint), dbAuthKey);
...
var jsonString = JsonConvert.SerializeObject(tick);
var jObject = JsonConvert.DeserializeObject(jsonString);
var response = DocumentClient.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), jObject);
response.Wait();

The enums are transformed into plain strings by this approach. Not very nice, but after all the frustration I'm content with this solution. And at least I didn't need to change any other place.

0
votes

You should use Azure Functions Cosmos DB output binding to save your document. The following example works as you expect (saves enum value as string).

Code run.csx:

#r "Newtonsoft.Json"

using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

public class Doc
{
    public string id { get; set; }

    [JsonConverter(typeof(StringEnumConverter))]
    public OrderActionEnum Action { get; set; }
}

public enum OrderActionEnum { NEW, CHANGE }

public static HttpResponseMessage Run(HttpRequestMessage req, out Doc outputDocument)
{
    outputDocument = new Doc { id = "111", Action = OrderActionEnum.CHANGE };
    return req.CreateResponse(HttpStatusCode.OK);
}

Bindings function.json:

{
  "bindings": [
    {
      "authLevel": "function",
      "name": "req",
      "type": "httpTrigger",
      "direction": "in"
    },
    {
      "name": "$return",
      "type": "http",
      "direction": "out"
    },
    {
      "type": "documentDB",
      "name": "outputDocument",
      "databaseName": "testdb",
      "collectionName": "test",
      "createIfNotExists": false,
      "connection": "my_DOCUMENTDB",
      "direction": "out"
    }
  ],
  "disabled": false
}

Produced document:

{
  "id": "111",
  "Action": "CHANGE"
}