18

I need to cache a generic list so I dont have to query the databse multiple times. In a web application I would just add it to the httpcontext.current.cache . What is the proper way to cache objects in console applications?

user990423
  • 1,391
  • 2
  • 11
  • 30
Tom Squires
  • 8,106
  • 9
  • 44
  • 68
  • possible duplicate of [How can I use System.Web.Caching.Cache in a Console application?](http://stackoverflow.com/questions/1037695/how-can-i-use-system-web-caching-cache-in-a-console-application) – Josh Sep 29 '11 at 15:35
  • 3
    @Josh: this is definitely *not* a duplicate of that question, since the question is "what is the *proper* way to cache objects in console applications". `System.Web.Caching.Cache` is not the proper way to cache objects in a console application. – MusiGenesis Sep 29 '11 at 15:38
  • 1
    @MusiGenesis: pre .Net 4.0 this was a perfectly valid way to do caching. http://www.hanselman.com/blog/UsingTheASPNETCacheOutsideOfASPNET.aspx – Josh Sep 29 '11 at 15:40
  • @Josh: am I missing something here? In ASP.NET, pages are stateless so keeping things in memory somewhere past the life of a page requires something like the System.Web cache. A console app has no such problem. Hanselman's blog post does not explain why using `System.Web.Caching.Cache` is necessary or desirable - it just states that it's possible (which it is, of course). – MusiGenesis Sep 29 '11 at 15:44
  • 1
    @MusiGenesis: The cache does a lot more than just "keep things in membory". It provides thread safety, and the ability to have items expire, both absolute and sliding. Using this inside any application is desirable when one want's to avoid making potentially expensive calls to retrieve data. – Josh Sep 29 '11 at 15:50

7 Answers7

16

Keep it as instance member of the containing class. In web app you can't do this since page class's object is recreated on every request.

However .NET 4.0 also has MemoryCache class for this purpose.

Muhammad Hasan Khan
  • 33,106
  • 14
  • 80
  • 125
7

In a class-level variable. Presumably, in the main method of your console app you instantiate at least one object of some sort. In this object's class, you declare a class-level variable (a List<String> or whatever) in which you cache whatever needs caching.

MusiGenesis
  • 71,592
  • 38
  • 183
  • 324
  • 4
    Some people will downvote just so there answer gets upvoted. I evened it out dont worry. – Gage Sep 29 '11 at 15:40
3

Here is a very simple cache class I use in consoles that has self clean up and easy implementation.

The Usage:

return Cache.Get("MyCacheKey", 30, () => { return new Model.Guide().ChannelListings.BuildChannelList(); });

The Class:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Timers;

    namespace MyAppNamespace
    {
        public static class Cache
        {
            private static Timer cleanupTimer = new Timer() { AutoReset = true, Enabled = true, Interval = 60000 };
            private static readonly Dictionary<string, CacheItem> internalCache = new Dictionary<string, CacheItem>();

            static Cache()
            {
                cleanupTimer.Elapsed += Clean;
                cleanupTimer.Start();
            }

            private static void Clean(object sender, ElapsedEventArgs e)
            {
                internalCache.Keys.ToList().ForEach(x => { try { if (internalCache[x].ExpireTime <= e.SignalTime) { Remove(x); } } catch (Exception) { /*swallow it*/ } });
            }

            public static T Get<T>(string key, int expiresMinutes, Func<T> refreshFunction)
            {
                if (internalCache.ContainsKey(key) && internalCache[key].ExpireTime > DateTime.Now)
                {
                    return (T)internalCache[key].Item;
                }

                var result = refreshFunction();

                Set(key, result, expiresMinutes);

                return result;
            }

            public static void Set(string key, object item, int expiresMinutes)
            {
                Remove(key);

                internalCache.Add(key, new CacheItem(item, expiresMinutes));
            }

            public static void Remove(string key)
            {
                if (internalCache.ContainsKey(key))
                {
                    internalCache.Remove(key);
                }
            }

            private struct CacheItem
            {
                public CacheItem(object item, int expiresMinutes)
                    : this()
                {
                    Item = item;
                    ExpireTime = DateTime.Now.AddMinutes(expiresMinutes);
                }

                public object Item { get; private set; }
                public DateTime ExpireTime { get; private set; }
            }
        }

}
rrrr
  • 2,207
  • 23
  • 51
AndrewD
  • 31
  • 2
  • Clean function get called after the specified interval, how can I call the function again, a new list should be updated? – bilal Jan 19 '17 at 07:43
2
// Consider this psuedo code for using Cache
public DataSet GetMySearchData(string search)
{
    // if it is in my cache already (notice search criteria is the cache key)
    string cacheKey = "Search " + search;
    if (Cache[cacheKey] != null)
    {
        return (DataSet)(Cache[cacheKey]);
    }
    else
    {
        DataSet result = yourDAL.DoSearch(search);
        Cache[cacheKey].Insert(result);  // There are more params needed here...
        return result;
    }
}

Ref: How do I cache a dataset to stop round trips to db?

Community
  • 1
  • 1
Scorpion
  • 4,222
  • 5
  • 35
  • 59
1

There a many ways to implement caches, depending of what exactly you are doing. Usually you will be using a dictionary to hold cached values. Here is my simple implementation of a cache, which caches values only for a limited time:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CySoft.Collections
{
    public class Cache<TKey,TValue>
    {
        private readonly Dictionary<TKey, CacheItem> _cache = new Dictionary<TKey, CacheItem>();
        private TimeSpan _maxCachingTime;

        /// <summary>
        /// Creates a cache which holds the cached values for an infinite time.
        /// </summary>
        public Cache()
            : this(TimeSpan.MaxValue)
        {
        }

        /// <summary>
        /// Creates a cache which holds the cached values for a limited time only.
        /// </summary>
        /// <param name="maxCachingTime">Maximum time for which the a value is to be hold in the cache.</param>
        public Cache(TimeSpan maxCachingTime)
        {
            _maxCachingTime = maxCachingTime;
        }

        /// <summary>
        /// Tries to get a value from the cache. If the cache contains the value and the maximum caching time is
        /// not exceeded (if any is defined), then the cached value is returned, else a new value is created.
        /// </summary>
        /// <param name="key">Key of the value to get.</param>
        /// <param name="createValue">Function creating a new value.</param>
        /// <returns>A cached or a new value.</returns>
        public TValue Get(TKey key, Func<TValue> createValue)
        {
            CacheItem cacheItem;
            if (_cache.TryGetValue(key, out cacheItem) && (DateTime.Now - cacheItem.CacheTime) <= _maxCachingTime) {
                return cacheItem.Item;
            }
            TValue value = createValue();
            _cache[key] = new CacheItem(value);
            return value;
        }

        private struct CacheItem
        {
            public CacheItem(TValue item)
                : this()
            {
                Item = item;
                CacheTime = DateTime.Now;
            }

            public TValue Item { get; private set; }
            public DateTime CacheTime { get; private set; }
        }

    }
}

You can pass a lambda expression to the Get method, which retrieves values from a db for instance.

BenMorel
  • 30,280
  • 40
  • 163
  • 285
Olivier Jacot-Descombes
  • 86,431
  • 10
  • 121
  • 160
1

You might be able to just use a simple Dictionary. The thing that makes the Cache so special in the web environment is that it persists and is scoped in such a way that many users can access it. In a console app, you don't have those issues. If your needs are simple enough, the dictionary or similar structures can be used to quickly lookup values you pull out of a database.

Daniel S
  • 11
  • 1
  • Actually the special thing about a Cache is the way it handles expiration, as opposed to the unique lookup of a Dictionary as you mentioned. – drzaus Oct 20 '16 at 13:13
0

Use Singleton Pattern.

http://msdn.microsoft.com/en-us/library/ff650316.aspx

Dariusz
  • 12,358
  • 6
  • 46
  • 66
  • 1
    If singleton behaviour is desired, best to use an IoC container (or similar), rather than using the Singleton pattern directly. It's pretty easy for the Singleton pattern to become an anti-pattern e.g. causing problems in testing. – Tim Lloyd Sep 29 '11 at 15:38
  • 4
    @chibac: It's a _Console_ app. IoC = YAGNI. – Henk Holterman Sep 29 '11 at 15:55
  • @Henk Possibly, that's impossible to say given the context. You obviously haven't come across the level of console apps I have. Surely you can think of an example of a console app which is possibly complex? If it is overkill, then that would be the "or similar". Console apps should be tested too. – Tim Lloyd Sep 29 '11 at 15:58
  • @chibacity Badly used IoC container can be worst than hand made Singleton. All we know that, but the question is about "how to cache", not how to deal with Singletons :) – Dariusz Sep 29 '11 at 17:44
  • @dario I am obviously not recommending using IoC containers badly, that would be stupid. If you use the Singleton pattern directly, you will have trouble testing. And I did say "or similar", I'm not being dogmatic... – Tim Lloyd Sep 29 '11 at 17:51