2

I'm trying to implement a cache (dictionary) with the key being a predicate (Func) and the value being the result of the Func (an IEnumerable).

I'm having a hard time figuring out a key for the dictionary. I tried GetHashCode (my intiution was that it was not going to work the way I hoped) but whatever predicate I pass, I always end up with the same hash code.

Here's what I have so far.

public partial class DataProvider
{
    ICache Cache = new Cache();

    static SemaphoreSlim dbLock = new SemaphoreSlim(1);
    static List<dynamic> CachedPredicates = new List<dynamic>();
    static object cacheLock = new object { };

    public async Task<IEnumerable<T>> FindItems<T>(Func<T, bool> predicate) where T : IDocumentModel
    {
        Func<T, bool> p;
        var lookUpDb = false;
        List<T> c = new List<T>();

        lock (cacheLock)
        {
            p = CachedPredicates.SingleOrDefault(x => x as Func<T, bool> == predicate);
            if (p == null)
            {
                CachedPredicates.Add(predicate);
                lookUpDb = true;
            }
        }

        if (lookUpDb)
        {
            await dbLock.WaitAsync();
            try
            {
                IDocumentQuery<T> query = AppDatabase.Client.CreateDocumentQuery<T>(UriFactory.CreateDocumentCollectionUri(ProjectCollection.DatabaseId, ProjectCollection.CollectionId))
                    .Where(predicate).AsQueryable()
                    .AsDocumentQuery();

                    while (query.HasMoreResults)
                    {
                        c.AddRange(await query.ExecuteNextAsync<T>());
                    }
                }
                finally
                {
                    dbLock.Release();
                }

                Cache.SetItems<T>(c, predicate.GetHashCode().ToString());
                return c;
            }
            else
            {
                return Cache.Items<T>(predicate.GetHashCode().ToString()); 
            }

        }
    }
}

And here's an example of how it would be used:

var v = await DataProvider.FindItems<DataTableModel>(x => x.ProjectId != string.Empty);

So the first time this is called, it would query the database, and subsequent calls would simply fetch the results from the Cache.

I have omitted the code from the Cache class, but will add it if someone sees the relevancy for it.

The idea is to not have to manage an ID for each results for each predicates passed to FindItems and simply "recognize" the query for what it is.

Is this in any way possible ?

Francis Ducharme
  • 4,848
  • 6
  • 43
  • 81
  • Why not let the caller pass in the key? Don't try to guess this, whatever you do will end up with potential duplicates. – DavidG Apr 18 '18 at 15:11
  • It sounds like you want to implement the Memoize pattern. I don't understand how the `Func` would be a key. It sounds like you want the `Func` to generate the result based on the actual key, and then not execute the `Func` a second time for later lookups. See this article: https://www.dotnetperls.com/memoization – JamesFaix Apr 18 '18 at 15:13
  • @DavidG Of course, but the caller would need to hold a cache of keys. I was trying to avoid that. – Francis Ducharme Apr 18 '18 at 15:13
  • Why would the caller need to cache anything? They would only need to have a list of consts for example. – DavidG Apr 18 '18 at 15:14
  • 2
    All other things aside - accepting `Func` here is wrong I think, because it will cause your azure query to evaluate on client. You should at least accept `Expression>`. And then there is such question: https://stackoverflow.com/q/283537/5311735 – Evk Apr 18 '18 at 15:21
  • @Evk The problem with testing equality of an Expression is that it may change for the same cache key. For example, let's say the function is to get the data up to a specific time and cache that for a certain duration. If the time changes (which might happen on every call) then so does the expression. – DavidG Apr 18 '18 at 15:27
  • @DavidG if time changes - then it's a different query and so different key, if I understood your example correctly. But maybe you meant something like `x => x.SomeTime <= DateTime.UtcNow`, which represents a different query on every invocation, but will be considered identical and cached. Anyway, in general I agree that all this is very vague. – Evk Apr 18 '18 at 15:33
  • @Evk The first one, but it wasn't a great example. It's why I suggested passing in a user defined key. Every caching tool I have used does the same thing. – DavidG Apr 18 '18 at 15:37
  • 2
    @DavidG But OP tries to build transparent (to the caller) caching. Forcing caller to provide some unique key for each query is of course an option (and maybe even the only reliable one in this case), but not very convinient, so no surpise that OP searches for a better solution. – Evk Apr 18 '18 at 15:42

0 Answers0