首页 > 解决方案 > 耐用实体 - 反例

问题描述

Total Azure Functions 新手在这里,但我觉得我花了几天时间自己研究这个,我只是错过了一些东西。我正在创建一个简单的计数器实体,可用于生成订单跟踪号:

入口点:

public static class Counter
    {
        [FunctionName("GetTrackingNumber")]
        public static async Task<IActionResult> Get(
            [HttpTrigger(AuthorizationLevel.Function, "get", Route = "GetTrackingNumber")] HttpRequest req,
            [DurableClient] IDurableEntityClient client,
            ILogger log
            )
        {
            var entityId = new EntityId(nameof(CounterEntity), "myCounter");

            await client.SignalEntityAsync<ICounterEntity>(entityId, proxy => proxy.Add(1));

            var stateResponse = await client.ReadEntityStateAsync<CounterEntity>(entityId);
            string trackingNumber = "";

            if(stateResponse.EntityExists)
            {
                trackingNumber = await stateResponse.EntityState.GetNextTrackingNumber();
            }

            return new OkObjectResult(trackingNumber);            
        }
    }

柜台实体:

public interface ICounterEntity
    {
        [Deterministic]
        public void Add(int amount);

        [Deterministic]
        public Task<string> GetNextTrackingNumber();

        [Deterministic]
        public Task Reset();
    }

    [JsonObject(MemberSerialization.OptIn)]
    public class CounterEntity : ICounterEntity
    {

        private readonly Random _random = new Random();

        [JsonProperty("value")]
        public int Value { get; set; }

        [JsonProperty("prefix")]
        public string Prefix { get; set; }

        [JsonProperty("prefixList")]
        public List<String> PrefixList { get; set; }

        public CounterEntity()
        {
            PrefixList = new List<string>();
            Prefix = RandomString(3);
            PrefixList.Add(Prefix);
        }

        public void Add(int amount)
        {
            Value += amount;
        }

        public Task<string> GetNextTrackingNumber()
        {
            var thisTrackingNumber = String.Concat(Prefix, "-", string.Format("{0:00000000}", Value));
            return Task.FromResult(thisTrackingNumber);
        }

        public Task Reset()
        {
            Value = 0;
            Prefix = RandomString(3);
            PrefixList.Add(Prefix);
            return Task.CompletedTask;
        }
   
        public string RandomString(int size, bool lowerCase = false)
        {
            var builder = new StringBuilder(size);
            for (var i = 0; i < size; i++)
            {
                var @char = (char)_random.Next(offset, offset + lettersOffset);
                builder.Append(@char);
            }

            return lowerCase ? builder.ToString().ToLower() : builder.ToString();
        }

        [FunctionName(nameof(CounterEntity))]
        public static Task Run([EntityTrigger] IDurableEntityContext ctx) => ctx.DispatchAsync<CounterEntity>();


    }

我将该功能发布到 Azure,看起来它可以工作(有点),但我完全不相信它是正确的。我第一次打电话给它,我得到一个空白的回应。第二天我运行它的前几次也发生了类似的事情——函数应用程序可能不得不启动,这很好,但是我从发送请求中收到的前两三个响应回来显示最后一个数字输出昨晚,然后它开始按预期增加。

任何在耐用实体方面有更多经验的人都可以看看这个并提出我可能做错了什么吗?近一周的搜索几乎没有产生任何有用的信息。

谢谢你的帮助!!!

标签: c#azureazure-durable-functionsazure-http-trigger

解决方案


您可能会得到一个空洞的回复,这是合理的。这里的问题是您只是向实体发出信号,但不能保证在您使用函数检查实体状态ReadEntityStateAsync时它已成功创建(它可能仍在内存中)。

ReadEntityStateAsync函数仅从某个较早的时间点返回实体状态的快照,但这种状态可能是陈旧的。.NET 页面中持久实体的开发人员指南对此进行了说明:

ReadEntityStateAsync 返回的对象只是一个本地副本,即来自某个较早时间点的实体状态的快照。特别是,它可能是陈旧的,修改此对象对实际实体没有影响。

只有编排可以准确地读取实体的状态。我之前采用的一种粗略的解决方法(在一个 HTTP 调用中完成所有这些操作)是启动一个编排(使用StartNewAsync的函数IDurableOrchestrationClient),然后在该编排中调用您的实体(而不是发出信号)。您将在该编排中等待以确保已创建实体。

StartNewAsync将返回一个instanceId,因此您可以使用WaitForCompletionOrCreateCheckStatusResponseAsync的函数来IDurableOrchestrationClient尝试等待您的编排完成。只有这样,您才应该使用读取实体状态ReadEntityStateAsync

Durable Functions 扩展公开了内置的 HTTP API。它还提供 API,用于与 HTTP 触发函数中的编排和实体进行交互。如果您不介意在客户端进行多次 HTTP 调用,您当然可以提出更好的解决方案。


推荐阅读