3

A follow-question to my previous post about Azure Functions. I need to update a document in DocumentDB using the imperative binder (Binder). I don't really understand the documentation and I can't find any examples (I more or less find one kind of example which is the TextWriter one). The documentation says I can bind to "out T" by I find no examples of this.

Say that the document looks like this before running the function:

{
    child: {
        value: 0
    }
}

And the functions looks like this:

var document = await binder.BindAsync<dynamic>(new DocumentDBAttribute("myDB", "myCollection")
{
    ConnectionStringSetting = "my_DOCUMENTDB",
    Id = deviceId
});

log.Info($"C# Event Hub trigger function processed a message: document: { document }");

document.value = 100;
document.child.value = 200;

log.Info($"Updated document: { document }");

According to the second logging row, the document isn't properly updated. The child is not updated (which existed when read from the store) and value is added. Either way, nothing is persisted. I've tried adding an Output in the function.json, but the compiler complains about it and the documentation states that you shouldn't have any.

What am I missing?

Kimmen
  • 717
  • 6
  • 18
  • @mathewc mentioned below that this scenario won't auto-update for you today. But can you share what your function.json looked like when you saw the compiler errors? I believe you should be able to use an output binding to perform the update -- you'd have to use `IAsyncCollector collector` as your parameter, then you'd call `collector.AddAsync(document)`. I'll try to set up a sample and post it below – brettsam Feb 02 '17 at 17:45
  • The compilation error was that the output was missing in the Run signature as an out argument, which is as expected. But I tried it according to this blog post: https://weblogs.asp.net/sfeldman/azure-functions-to-make-audit-queue-and-auditors-happy where he seem to declare an output without having it as an out argument. – Kimmen Feb 02 '17 at 20:01

2 Answers2

5

Mathew's sample (using DocumentClient) works, but I wanted to clarify the other way you can do it with Binder and an output binding.

You're bumping into two issues:

  1. The Document implementation of dynamic appears to return a new object instance every time you request a child object. This isn't related to Functions, but explains why document.child.value = 200 doesn't work. You are updating one instance of child that is not actually attached to the document. I'll try to double-check this with DocumentDb folks, but that is confusing. One way around this is to request a JObject instead of a dynamic. My code below does that.

  2. As @mathewc pointed out, Binder does not auto-update the document. We'll track that in the issue he filed. Instead, you can use an output binding with IAsyncCollector<dynamic> to update the document. Behind-the-scenes we'll call InsertOrReplaceDocumentAsync, which will update the document.

Here's a full sample that worked for me:

Code:

#r "Microsoft.Azure.WebJobs.Extensions.DocumentDB"
#r "Newtonsoft.Json"

using System;
using Newtonsoft.Json.Linq;

public static async Task Run(string input, Binder binder, IAsyncCollector<dynamic> collector, TraceWriter log)
{        
    string deviceId = "0a3aa1ff-fc76-4bc9-9fe5-32871d5f451b";
    dynamic document = await binder.BindAsync<JObject>(new DocumentDBAttribute("ItemDb", "ItemCollection")
    {
        ConnectionStringSetting = "brettsamfunc_DOCUMENTDB",
        Id = deviceId
    });

    log.Info($"C# Event Hub trigger function processed a message: document: { document }");

    document.value = 100;
    document.child.value = 200;

    await collector.AddAsync(document);
    log.Info($"Updated document: { document }");
}

binding:

{
  "type": "documentDB",
  "name": "collector",
  "connection": "brettsamfunc_DOCUMENTDB",
  "direction": "out",
  "databaseName": "ItemDb",
  "collectionName": "ItemCollection",
  "createIfNotExists": false
}
brettsam
  • 2,702
  • 1
  • 15
  • 24
  • This is a suitable workaround, thanks a lot. I was looking at the IAsyncCollector type earlier, but I was looking for an Update method and didn't investigate the AddAsync. It's a bit misleading name though, but I guess this isn't the proper way of updating the Document. – Kimmen Feb 02 '17 at 20:09
2

Yes, I do believe there is an issue here, and I've logged a bug here in our repo to track it.

To work around this until we fix it, you can bind to and use the DocumentClient directly to perform the update, e.g.:

public static async Task Run(
    string input, Binder binder, DocumentClient client, TraceWriter log)
{
    var docId = "c31d48aa-d74b-46a3-8ba6-0d4c6f288559";
    var document = await binder.BindAsync<JObject>(
                new DocumentDBAttribute("ItemDb", "ItemCollection")
    {
        ConnectionStringSetting = "<mydb>",
        Id = docId
    });

    log.Info("Item before: " +  document.ToString());
    document["text"] = "Modified!";

    var docUri = UriFactory.CreateDocumentUri("ItemDb", "ItemCollection", docId);
    await client.ReplaceDocumentAsync(docUri, document);
}

However, once you're using DocumentClient directly like this, it might turn out that you can just use it directly for all of your operations, your call. For example:

public static async Task Run(
    string input, DocumentClient client, TraceWriter log)
{
    var docId = "c31d48aa-d74b-46a3-8ba6-0d4c6f288559";
    var docUri = UriFactory.CreateDocumentUri("ItemDb", "ItemCollection", docId);

    var response = await client.ReadDocumentAsync(docUri);
    dynamic document = response.Resource;

    log.Info("Value: " +  dynamic.text);
    document.text = "Modified!"; 

    await client.ReplaceDocumentAsync(docUri, document);
}
mathewc
  • 13,312
  • 2
  • 45
  • 53
  • 1
    This time I'm going for the solution from @brettsam below. But I guess it's interesting to use DocumentClient for querying scenarios, which I more likely going to use in the future. What are the pros and cons in using the DocumentClient instead of Binder (once the issue with Binder has been resolved)? – Kimmen Feb 02 '17 at 20:16