Search code examples
c#.net.net-8.0frozensetc#-12.0

Issue with FrozenSet Preserving Order in Asynchronous Data Retrieval Method using .NET 8


I am currently facing an issue with the FrozenSet in C# with .NET 8 Web API where it appears to be managing the order of data unexpectedly. Below is the method where I am encountering this behavior:

private async Task<FrozenSet<DemoDataRes>> GetDataAsync(int Id, string cacheKey, CancellationToken cancellationToken)
{
    var results = _cacheProvider.Get<FrozenSet<DemoDataRes>>(cacheKey, CacheKeys.DEMOCACHEGROUP);
    if (results is null)
    {
        results = (await (from avt in _DemoDbContext.Catalogs
                          where avt.Id == Id
                          orderby avt.Id
                          select new DemoDataRes
                          {
                              Id = avt.Id,
                              Type = avt.Name,
                          }
                         ).ToListAsync(cancellationToken));
        var value = results.ToFrozenSet();
        _cacheProvider.Set(cacheKey, value, CacheKeys.DEMOCACHEGROUP, Defaults.CacheExpiryMinutes);
    }
    return results;
}

The method is supposed to asynchronously fetch data from a database context, map it to a DemoDataRes object, and store it in a cache. If the cache is empty, it retrieves the data from the database, orders it by the Id, and then converts this list to a FrozenSet before caching it.

Issue: I expect the FrozenSet to manage or preserve the order of the items that are fetched from the database with orderby Id. However, the FrozenSet seems not to be preserving the order of insertion, which is not the intended behavior for sets in general.

Is this an expected behavior of FrozenSet in certain circumstances or could it be influenced by how the data is being inserted or retrieved?Are there any known issues or considerations with FrozenSet in C# that might cause it to behave like this? What are the best practices for ensuring that a collection does not manage or preserve order when caching data objects?

I did one more sample POC with the following code and found the ordering it maintained. Not sure why the code that returns data from database is not maintaining the order while converting it to FrozenSet.

Working Example :

public async Task<FrozenSet<DemoDataRes>> GetDataAsync(CancellationToken cancellationToken)
{
     List<DemoDataRes> lstitem = new List<DemoDataRes>();
     DemoDataRes item1 = new DemoDataRes();
     item1.Type = "one";
     item1.CatalogId = 100;
     lstitem.Add(item1);
 
     DemoDataRes item2 = new DemoDataRes();
     item2.Type = "two";
     item2.CatalogId = 21;
     lstitem.Add(item2);
 
     DemoDataRes item3 = new DemoDataRes();
     item3.Type = "three";
     item3.CatalogId = 34;
     lstitem.Add(item3);
 
     DemoDataRes item4 = new DemoDataRes();
     item4.Type = "four";
     item4.CatalogId = 49;
     lstitem.Add(item4);
     lstitem = await Task.FromResult(lstitem.OrderByDescending(x => x.CatalogId).ToList());
     FrozenSet<FlexInspReportControlDataRes> frozenSet = lstitem.ToFrozenSet();
     return frozenSet;
}

Can anyone please help me here by providing their guidance? Any help would be greatly appreciated.


Solution

  • I expect the FrozenSet to manage or preserve the order of the items that are fetched from the database with orderby Id

    That is not what FrozenSet is designed for. It is:

    optimized for situations where a set is created infrequently but is used frequently at run time. It has a relatively high cost to create but provides excellent lookup performance.

    As many other collections designed for lookup performance it does not make any guarantees about preserving the order of elements. For example from the Supplemental API remarks: HashSet doc (emphasis mine):

    The HashSet<T> class provides high-performance set operations. A set is a collection that contains no duplicate elements, and whose elements are in no particular order.

    If order or element duplication is more important than performance for your application, consider using the List<T> class together with the Sort method.

    and actually internally ToFrozenSet will construct HashSet<T> from the incoming data, so the order is not guaranteed to be maintained.

    As the docs say - if order is important then use List<>.

    Other options would be:

    1. Cache two collections (an ordered one + the set)
    2. Use something like SortedSet<T>, but you should note that methods like Contains on it are O(log n)
    3. Use dictionary with the index of ordered element as value/part of value.