Search code examples
c#azureazure-functionsazure-table-storageazure-durable-functions

Azure Durable Function doesn't create a storage table entity


I am having a durable function, that I'd like to use to aggregate calculation results as soon as they enter a storage queue. Also, I want the function to create a table entity on an Azure storage table called "result".

The function works as expected, but no single table entity is getting created. However, when I call my test trigger TestTrigger via https://func-foobar.azurewebsites.net/api/create? a table entry gets created.

Why is that? What am I doing wrong here?

namespace Aggregator
{
    public static class AggregatorFunction
    {
        public class ResultEntity
        {
            public string PartitionKey { get; set; }

            public string RowKey { get; set; }

            public uint Run { get; set; }

            public decimal IntermediatePi { get; set; }
        }

        [FunctionName("Counter")]
        [return: Table("result")]
        public static ResultEntity Counter
        (
            [EntityTrigger] IDurableEntityContext ctx,
            ILogger log
        )
        {
            switch (ctx.OperationName.ToLowerInvariant())
            {
                case "add":
                    var unit = ctx.GetInput<UnitOfWork>();
                    log.LogInformation($"Processing: {unit}");

                    (decimal IntermediatePi, ulong IntermediateHits, uint run) = ctx.GetState<(decimal, ulong, uint)>();

                    IntermediateHits += unit.NumHits;
                    IntermediatePi = (decimal)IntermediateHits / unit.TotalRuns * 4.0m;
                    run += 1;

                    log.LogInformation($"==> State: run={run} intermediateHits={IntermediateHits} intermediatePi={IntermediatePi}");
                    ctx.SetState((IntermediatePi, IntermediateHits, run));

                    return new ResultEntity
                    {
                        PartitionKey = "Pi",
                        RowKey = Guid.NewGuid().ToString(),
                        Run = run,
                        IntermediatePi = IntermediatePi
                    };

                case "delete":
                    log.LogInformation("Deleting state!");
                    ctx.DeleteState();

                    return null;
            }

            return null;
        }

        [FunctionName("QueueTrigger")]
        public static async Task Run
        (
            [QueueTrigger(queueName: "results")] string json,
            [DurableClient] IDurableEntityClient entityClient
        )
        {
            var unit = JsonSerializer.Deserialize<UnitOfWork>(json);
            var entityId = new EntityId("Counter", "CounterKey");
            await entityClient.SignalEntityAsync(entityId, "add", unit);
        }

        [FunctionName("HttpResetTrigger")]
        public static async Task Reset
        (
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "reset")] HttpRequest req,
            [DurableClient] IDurableEntityClient entityClient
        )
        {
            var entityId = new EntityId("Counter", "CounterKey");
            await entityClient.SignalEntityAsync(entityId, "delete", null);
        }

        [FunctionName("TestTrigger")]
        [return: Table("result")]
        public static ResultEntity CreateTable
        (
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "create")] HttpRequest req
        )
        {
            return new ResultEntity
            {
                PartitionKey = "Test",
                RowKey = Guid.NewGuid().ToString(),
                Run = 1,
                IntermediatePi = ulong.MaxValue
            };
        }
    }
}   

Solution

  • Haven't tried something like that but the docs has this to say (https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-bindings?tabs=csharp#trigger-behavior):

    Warning

    Orchestrator functions should never use any input or output bindings other than the orchestration trigger binding. Doing so has the potential to cause problems with the Durable Task extension because those bindings may not obey the single-threading and I/O rules. If you'd like to use other bindings, add them to an activity function called from your orchestrator function. For more information about coding constraints for orchestrator functions, see the Orchestrator function code constraints documentation.

    So the right approach here would be to use an activity function for the table entity write.