how to cancel expiration of cached item - c#

I have added one cache item in MemoryCache which is expired after every 10 minutes. But when it is expired I am calling one method which fetches the fresh data from the database & again Set the new cache item in MemoryCache with the same key. But if the exception is thrown in the Cache item removed call back method then item is removed from MemoryCache but new item is not getting added to cache. This cache item is used in the further operations.
How to handle this situation?

Instead of having the cache data refreshed automaticaly when it expires put the GetFromDataBase call in with the GetData Method and call it if the cache returns nothing. That way the exception is passed back to the calling code and the next call will (hopefully) re populate the cache
ie (please excuse psudocode)
public class data
{
public SomeData GetData(int id)
{
if (cache.Contains(id))
{
return cache[id];
}
else
{
SomeData data = GetDataFromDB(id);
cache[id] = data;
return data;
}
}
}

Related

Store object in session variables

I have a dropdown menu that when you select an option value submit the form, and to avoid repetitive database calls I am storing my non-sensitive object in a session.
private List<Employee> stafflist
{
get { return Session["stafflist"] as List<Employee>; }
set { Session["stafflist"] = new Employee(); }
}
private void RemoveStaff()
{
Session.Remove("stafflist");
}
however in my
[HttpPost]
public ActionResult index (...)
{
//why can't I get the list of staff like this?
ViewBag.staff=stafflist.Where(..).toList();
//is the below still needed? i thought i
//have a session variable declare above,
//and to avoid 30x repetitive db calls?
//also note when i include the below the code runs fine,
//however, if i take it out it doesn't. i would like to avoid repetitive db calls
stafflist=db.Employee.toList();
}
First of all, you should not prevent to query the database. Proper caching is hard to get right, and a database is perfectly capable of performing queries and caching data.
If you're absolutely sure you want to circumvent the database, and query clientside (i.e. in the controller) then you need to pull the entire staff list from the database at least once per visitor.
You could do that in the first GET call to this controller, assuming the user will always visit that:
[HttpGet]
public ActionResult Index (...)
{
var cachedStaff = db.Employee.toList();
Session["stafflist"] = cachedStaff;
}
Then in the POST, where you actually want to do the database query (again, consider letting the database do what it's good at), you can query the list from the session:
[HttpPost]
public ActionResult Index (...)
{
var cachedStaff = Session["stafflist"] as List<Employee>();
// TODO: check cachedStaff for null, for when someone posts after
// their session expires or didn't visit the Index page first.
var selectedStaff = cachedStaff.Where(..).ToList();
// the rest of your code
}
Then the property you introduced can be used as syntactic sugar to clean up the code a bit:
private List<Employee> CachedStaff
{
get { return Session["stafflist"] as List<Employee>; }
set { Session["stafflist"] = value; }
}
[HttpGet]
public ActionResult Index (...)
{
CachedStaff = db.Employee.toList();
}
[HttpPost]
public ActionResult Index (...)
{
// TODO: this will throw an ArgumentNullException when
// the staff list is not cached, see above.
var selectedStaff = CachedStaff.Where(..).ToList();
// the rest of your code
}
A session is unique for the current user and the current session. That means that when the user closes the browser, the session information is lost. The session is also lost if the session cookie is removed. Read about state management.
If you want to have a global staff list that is available for all users you need to use something else. Caching is the most common case then.
you probably have it already figured it out, just in case I leave here what it worked for me.
First you create a new session variable based on an object created (in this case the object usr will be empty):
User usr = new User();
Session["CurrentUSR"]=usr;
where you want to use the new object, you will have to cast the session variable and point it to a new object created in that particular page:
User usr= new User(); //at this point the usr object is empty, now you are going to replace this new empty object with the session variable created before
usr=Session["CurrentUSR"] as User();
In case you have a list, the best course of action would be to create a List<> of that particular object.

TempData not null after refresh

I thought TempData was supposed to become null after one refresh or page redirect. It takes two refreshes of my page to clear the data though which isn't what I want, how do I make it go null after 1 refresh/redirect?
#using (Html.BeginForm())
{
<div class="form-group">
<button class="btn btn-default" type="submit">test</button>
</div>
}
public void test()
{
List<int> integers = new List<int>();
integers.Add(10);
integers.Add(20);
//First Refresh and myList still has values when I want it to be null
List<int> myList = (List<int>)TempData["test"]; // Take the value from the current data variable
if (myList == null) // Not yet stored in session, create a new list and store it as a session variable
{
myList = new List<int>();
TempData.Add("test", myList);
}
myList.AddRange(integers); // Add a new entry
}
I recently went through its source code to find out how TempData works.
So the lifespan of tempdata is rather unusual as it is one request only. In order to achieve this it maintains 2 HashSets to manage keys as well as the data dictionary:
private Dictionary<string, object> _data;
private HashSet<string> _initialKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private HashSet<string> _retainedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
When you read some data using an indexer or TryGetValue method it removes that key from _initalKeys collection.
public bool TryGetValue(string key, out object value)
{
_initialKeys.Remove(key);
return _data.TryGetValue(key, out value);
}
The actual dictionary that holds the data is intact at this point. That's why we can read same data consecutively without any issues. It only removes the key from _initialKeys collection, basically marking it to be deleted when the data is persisted.
If you want your values in TempData last longer you can use Peek and Keep methods. What Peek does is return the value without removing it from the _initialKeys:
public object Peek(string key)
{
object value;
_data.TryGetValue(key, out value);
return value;
}
Alternatively, you can call Keep method. Similarly it doesn't manipulate the data directly but just marks the key to be persisted by adding it to the _retainedKeys collection.
public void Keep(string key)
{
_retainedKeys.Add(key);
}
And it finally saves the data (to Session by default) by calling provider's Save method:
public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
{
_data.RemoveFromDictionary((KeyValuePair<string, object> entry, TempDataDictionary tempData) =>
{
string key = entry.Key;
return !tempData._initialKeys.Contains(key)
&& !tempData._retainedKeys.Contains(key);
}, this);
tempDataProvider.SaveTempData(controllerContext, _data);
}
So only the data that remained in the _initialKeys collection (unread) and the ones that are specifically instructed to retain (the keys in the _retainedKeys collection) will be saved. Hence the lifespan!
Reference: TempDataDictionary source code
In Asp.Net MVC we have different techniques for state management like Viewbag,ViewData and TempData.
TempData is something special in the sense that it can hold the value even for multiple successive requests depending upon how the value is being Read in the view.
If it is a normal read then the value will become null for next request.
2.If it is a Peek read , like if you have used Peek() method of TempData then value will be retain for the next request.
3.If it is a Keep read, meaning you have used Keep() method of TempData then also the Value will be available for next request.
4.If you have not read the value in the view then the value will be retain untill it is not read.
TempData in MVC with example
If you want to keep value in TempData object after request completion, you need to call Keep method with in the current action.
tutorial

How to trigger the removal of old cached values using MemoryCache as it was intended

I am using the following code in order to ensure that I only go to the database once for my Agent data and for the cached data to be refereshed when the contractId being passed in changes.
public static AgentCacher
{
private IAgentDal AgentDal;
private readonly ObjectCache AgentObjectCache;
private string LastContractId;
public AgentCacher(IAgentDal agentDal)
{
this.AgentDal = agentDal;
// Get the instance of the cache
this.AgentObjectCache = MemoryCache.Default;
}
public List<Agent> GetAgentsForContract(int contractId)
{
// Set the key to be used for the cache
var cacheKey = contractId.ToString(CultureInfo.InvariantCulture);
// Has the contract ID changed?
if (this.LastContractId !== cacheKey)
{
// Remove the item from the cache
this.AgentObjectCache.Remove(this.LastContractId);
}
// Are the agents for this contract ID already in the cache?
else if (this.AgentObjectCache.Contains(cacheKey))
{
// Return agents list from the cache
return
this.AgentObjectCache.Get(cacheKey) as
List<Agent>;
}
// Go to the database and get the agents
var agentsFromDatabase = this.AgentDal.GetAgentsForContract(contractId);
// Add the values to the cache
this.AgentObjectCache.Add(cacheKey, agentsFromDatabase, DateTimeOffset.MaxValue);
// Set the contract Id for checking next time
this.LastContractId = cacheKey;
// Return the agents
return agentsFromDatabase;
}
}
This works OK, but I feel like I'm probably not using the MemoryCache in the way it was intended to be used.
How can I trigger the removal of the values that I add to the cache to clear out the old values when the contractId changes, do I have to use ChangeMonitor or CacheItemPolicy that can be passed in when adding to the cache?
I've been struggling to find examples as to how it should be used properly.
Your logic looks right. However you are managing cache lifetime yourself instead of relying on built in expiration system technics. For instance instead of you to check if there is a new contractId, remove old one and add new one, I think you should cache for as many contractIds as needed, but to have for example absolute expiration for 1 hour. For example if there is contractId == 1 then you will have cache with cache key 1 and if another request asks for contractId == 2 then you will go to db pull contract information for id == 2 and store it in the cache for another absolute expiration 1 hour or so. I think this will be more efficient instead of you manage cache (add, remove) yourself.
You also need to consider locking data when you add and remove data from the cache in order to eliminate race condition.
You can find good example on how to do it:
Working With Caching in C#
Using MemoryCache in .NET 4.0

Update Cache with one item C#

I am getting users and their data from external webservice. I cache those items because I don't want to hit web service every time. Now, If user update any of their information, I am saving it through webservice. But I don't want to get the latest data from web service as it takes lot of time. Instead I want to update my cache. Can I do that ? If so, what would be the best way ? Here is my Code
List<User> users = appSecurity.SelectUsers();
var CacheKey = string.Format("GetUserList_{0}", currentUser);
CacheFactory.AddCacheItem(CacheKey, users, 300);
CacheFactory is a class where I handle Adding, Clearing and Removing cache. Below is the code
public static void RemoveCacheItem(string key)
{
Cache.Remove(key);
}
public static void ClearCache()
{
System.Collections.IDictionaryEnumerator enumerator = Cache.GetEnumerator();
while (enumerator.MoveNext())
{
RemoveCacheItem(enumerator.Key.ToString());
}
}
public static void AddCacheItem<T>(string key, T value, double timeOutInSeconds)
{
var Item = GetCacheItem<T>(key);
if (Item != null)
{
RemoveCacheItem(key);
Item = value;
}
Cache.Insert(key, value, null, DateTime.Now.AddSeconds(timeOutInSeconds), System.Web.Caching.Cache.NoSlidingExpiration);
}
The answer is yes, it can be done. It can also be done in many different ways depending on what you want to solve. At the basic level you can create a cache by using a List<T> or Dictionary<T,T> to store your data.
When you get information from the external web-service, you push the data into your List or Dictionary. You can then use that data throughout your application. When you need to update that cache, you update the value in the List/Dictionary.
You can update your dictonary like so
Dictionary<string, int> list = new Dictionary<string, int>();
then you can set the value for the key "test" as follows
list["test"] = list["test"] + 1;
When you are ready to push the updated data to the external source. All you need to do is properly parse that data into the format the source is expecting and send away.
Like I said there are many different ways to do this, but this is a basic sample way to accomplishing it. You can use this example to build off and go from there.

ASP.net Cacheing - Proper Usage

I am creating a web application and am having an issue with my cacheing.
My application has a large amount of data that I want to try and not call from the sql database everytime i need the info.
I have tried to use caching in the following way:
public static List<DAL.EntityClasses.AccountsEntity> Accounts
{
get
{
if (HttpContext.Current.Cache["Account"] == null)
{
HttpContext.Current.Cache.Insert("Account", LoadAccounts(), null, DateTime.Now.AddHours(4), System.Web.Caching.Cache.NoSlidingExpiration);
}
return (List<DAL.EntityClasses.AccountsEntity>)HttpContext.Current.Cache["Account"];
}
}
The problem is that it appears that as I am adding items to the Cache, the items that I have already cached get removed.
So most calls are calling the DB to get the data for the cache.
Where have I gone wrong?
Thanks
This is normal for a LRU cache - least used items get pushed out as the cache fills up capacity.
Configure your cache to larger amounts of data.
Just FYI:
Theres a problem with your implementation of the Accounts property, that is not releated to your original question, but may cause problems in the future:
What could happen is that between this line
if (HttpContext.Current.Cache["Account"] == null)
and this line:
return (List<DAL.EntityClasses.AccountsEntity>)HttpContext.Current.Cache["Account"];
your cache could be cleared / the Account entry could be deleted from the cache.
a better implementation would be:
public static List<DAL.EntityClasses.AccountsEntity> Accounts
{
get
{
List<DAL.EntityClasses.AccountsEntity> accounts =
HttpContext.Current.Cache["Account"] as List<DAL.EntityClasses.AccountsEntity>
if(accounts == null)
{
accounts = LoadAccounts();
HttpContext.Current.Cache.Insert("Account", accounts, null, DateTime.Now.AddHours(4), System.Web.Caching.Cache.NoSlidingExpiration);
}
return accounts;
}
}

Categories