Search code examples
c#linqcachingiteratordeferred-execution

Cache a Linq Query - is this possible?


So, this may seem like an edge case, but I'm simply wondering if this is possible. What I have is a combination of a static set and deferred set wrapped in an IEnumerable - for example:

public IEnumerable<T> MakeMyQuery<T>()
{
    // returns a List<T>
    var someStaticData = LoadMyStaticDataFromDatabase();

    // Returns IEnumerable<T>, but from another resource
    var deferredQuery = CreateADeferredQueryUsingYieldReturn(); 

    return someStaticData.Concat(deferredQuery);
}

So what happens here is that when I call .Take(someNumber) on my enumerable, it will return elements from my static Data first before attempting to evaluate the deferred component - effectively, I have "hidden" some potentially time consuming producing tasks behind the enumerable such that if I never need to get those elements, they actually never get evaluated because of the deferred nature of LINQ.

However, I don't think it's possible to cache this query for later use (I don't believe the state of the Iterator will be kept in cache, right?) Or is there an easy way to do this without enumerating the results to save?

Ideally, my flow would be like so:

public List<T> SomeMethod<T>(int numberOfGuys)
{
     IEnumerable<T> query = null;

     if(// Is in Cache)
       query = Cache["MyQuery"];
     else
     {
         query = MakeMyQuery();
         Cache["MyQuery"] = query;
     }

     return query.Take(numberOfGuys).ToList();
}

So I can re-use the same query over and over to request data, but potentially never have to requery the DB. Is there a way to do this?


Solution

  • I think you want to cache the result of query.Take(numberOfGuys).ToList() in a new List. Before calling MakeMyQuery() you could look at the number of elements in your cached list (if it exists) and if the number of elements in your cached list is greater or equal to numberOfGuys then you'd return the numberOfGuys from your cached list. Otherwise you'd replace your cached list with the new result of query.Take(numberOfGuys).ToList().

    As default.krammer pointed out, what you probably really want to cache is the result of LoadMyStaticDataFromDatabase() since if numberOfGuys is always less than LoadMyStaticDataFromDatabase(), you will end up hitting the DB repeatedly until numberOfGuys is greater than the number returned by LoadMyStaticDataFromDatabase(). So you could do a combination of caching LoadMyStaticDataFromDatabase() in the MakeMyQuery<T>() method and also caching query.Take(numberOfGuys).ToList() in SomeMethod<T>(int numberOfGuys), which would allow you to only hit the DB once, but still take advantage of the deferred execution of your IEnumerable<T>.