2

I am trying to use a durable entity in my Azure Function to cache some data. However, when I try to retrieve the entity (state) for the first time, I get an exception indicating an issue during the entity deserialization.

Here is my entity class and related code

[JsonObject(MemberSerialization.OptIn)]
public class ActionTargetIdCache : IActionTargetIdCache
{

    [JsonProperty("cache")]
    public Dictionary<string, ActionTargetIdsCacheItemInfo> Cache { get; set; } = new Dictionary<string, ActionTargetIdsCacheItemInfo>();

    public void CacheCleanup(DateTime currentUtcTime)
    {
        foreach (string officeHolderId in Cache.Keys)
        {
            TimeSpan cacheItemAge = currentUtcTime - Cache[officeHolderId].lastUpdatedTimeStamp;

            if (cacheItemAge > TimeSpan.FromMinutes(2))
            {
                Cache.Remove(officeHolderId);
            }
        }
    }

    public void DeleteActionTargetIds(string officeHolderId)
    {
        if (this.Cache.ContainsKey(officeHolderId))
        {
            this.Cache.Remove(officeHolderId);
        }
    }

    public void DeleteState()
    {
        Entity.Current.DeleteState();
    }


    public void SetActionTargetIds(ActionTargetIdsCacheEntry entry)
    {
        this.Cache[entry.Key] = entry.Value;
    }

    public Task<ActionTargetIdsCacheItemInfo> GetActionTargetIdsAsync(string officeHolderId)
    {
        if (this.Cache.ContainsKey(officeHolderId))
        {
            return Task.FromResult(Cache[officeHolderId]);
        }
        else
        {
            return Task.FromResult(new ActionTargetIdsCacheItemInfo());
        }
    }
    // public void Reset() => this.CurrentValue = 0;
    // public int Get() => this.CurrentValue;

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

public class ActionTargetIdsCacheEntry
{
    // officeHolderId
    public string Key { get; set; } = string.Empty;
    public ActionTargetIdsCacheItemInfo Value { get; set; } = new ActionTargetIdsCacheItemInfo();
}

[JsonObject(MemberSerialization.OptIn)]
public class ActionTargetIdsCacheItemInfo : ISerializable
{
    public ActionTargetIdsCacheItemInfo()
    {
        lastUpdatedTimeStamp = DateTime.UtcNow;
        actionTargetIds = new List<string>();
    }

    public ActionTargetIdsCacheItemInfo(SerializationInfo info, StreamingContext context)
    {
        lastUpdatedTimeStamp = info.GetDateTime("lastUpdated");
        actionTargetIds = (List<string>)info.GetValue("actionTargetIds", typeof(List<string>));
    }

    [JsonProperty]
    public DateTimeOffset lastUpdatedTimeStamp { get; set; } = DateTimeOffset.UtcNow;
    [JsonProperty]
    public List<string> actionTargetIds { get; set; } = new List<string>();

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("lastUpdated", lastUpdatedTimeStamp);
        info.AddValue("actionTargetIds", actionTargetIds);
    }
}

    public interface IActionTargetIdCache
{
    void CacheCleanup(DateTime currentUtcTime);
    void DeleteActionTargetIds(string officeHolderId);

    void DeleteState();
    void SetActionTargetIds(ActionTargetIdsCacheEntry item);
    // Task Reset();
    Task<ActionTargetIdsCacheItemInfo> GetActionTargetIdsAsync(string officeHolderId);
    // void Delete();
}

Here is the exception I get during the first attempt to access the state from an orchestration using the GetActionTargetIdsAsync method:



Exception has occurred: CLR/Microsoft.Azure.WebJobs.Extensions.DurableTask.EntitySchedulerException
Exception thrown: 'Microsoft.Azure.WebJobs.Extensions.DurableTask.EntitySchedulerException' in System.Private.CoreLib.dll: 'Failed to populate entity state from JSON: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'PolTrack.CdbGetFunctionApp.ActionTargetIdsCacheItemInfo' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path 'cache.officeHolderId1', line 1, position 29.'
 Inner exceptions found, see $exception in variables window for more details.
 Innermost exception     Newtonsoft.Json.JsonSerializationException : Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'PolTrack.CdbGetFunctionApp.ActionTargetIdsCacheItemInfo' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.
To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. ICollection, IList) like List<T> that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.
Path 'cache.officeHolderId1', line 1, position 29.

Could someone with the sufficient SO privileges please add the tag azure-durable-entities.

ebhh2001
  • 2,034
  • 4
  • 18
  • 27
  • I'm not sure if your class - which looks kinda complex - fulfills the requirements here? Have you checked that? https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-dotnet-entities#class-requirements – silent Feb 04 '20 at 19:16
  • @silent thanks. Yes, I have read that. I not an expert on JSON.net, and I was hoping someone would provide a hint on what I need to change there. Why do you say the class looks kinda complex? It has only one state property (the dictionary) and a few methods to modify the state. – ebhh2001 Feb 04 '20 at 20:35
  • 1
    hm maybe this part? "Entity classes are POCOs (plain old CLR objects) that require no special superclasses, interfaces, or attributes" You are implementing interfaces. Could you try to remove those and see if that helps? – silent Feb 04 '20 at 20:39

1 Answers1

1

I did manage to get around this by following @silent suggestion. I re-designed the entity class to only use CLR types. In my case, that meant replacing Dictionary<string, ActionTargetIdsCacheItemInfo> with two dictionaries Dictionary<string, List<string>> and Dictionary<string, DateTimeOffset>.

ebhh2001
  • 2,034
  • 4
  • 18
  • 27