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.
Related
I have a requirement where we need a plugin to retrieve a session id from an external system and cache it for a certain time. I use a field on the entity to test if the session is actually being cached. When I refresh the CRM form a couple of times, from the output, it appears there are four versions (at any time consistently) of the same key. I have tried clearing the cache and testing again, but still the same results.
Any help appreciated, thanks in advance.
Output on each refresh of the page:
20170511_125342:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125410:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125342:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125437:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125358:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
20170511_125437:1:55a4f7e6-a1d7-e611-8100-c4346bc582c0
To accomplish this, I have implemented the following code:
public class SessionPlugin : IPlugin
{
public static readonly ObjectCache Cache = MemoryCache.Default;
private static readonly string _sessionField = "new_sessionid";
#endregion
public void Execute(IServiceProvider serviceProvider)
{
var context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
try
{
if (context.MessageName.ToLower() != "retrieve" && context.Stage != 40)
return;
var userId = context.InitiatingUserId.ToString();
// Use the userid as key for the cache
var sessionId = CacheSessionId(userId, GetSessionId(userId));
sessionId = $"{sessionId}:{Cache.Select(kvp => kvp.Key == userId).ToList().Count}:{userId}";
// Assign session id to entity
var entity = (Entity)context.OutputParameters["BusinessEntity"];
if (entity.Contains(_sessionField))
entity[_sessionField] = sessionId;
else
entity.Attributes.Add(new KeyValuePair<string, object>(_sessionField, sessionId));
}
catch (Exception e)
{
throw new InvalidPluginExecutionException(e.Message);
}
}
private string CacheSessionId(string key, string sessionId)
{
// If value is in cache, return it
if (Cache.Contains(key))
return Cache.Get(key).ToString();
var cacheItemPolicy = new CacheItemPolicy()
{
AbsoluteExpiration = ObjectCache.InfiniteAbsoluteExpiration,
Priority = CacheItemPriority.Default
};
Cache.Add(key, sessionId, cacheItemPolicy);
return sessionId;
}
private string GetSessionId(string user)
{
// this will be replaced with the actual call to the external service for the session id
return DateTime.Now.ToString("yyyyMMdd_hhmmss");
}
}
This has been greatly explained by Daryl here: https://stackoverflow.com/a/35643860/7708157
Basically you are not having one MemoryCache instance per whole CRM system, your code simply proves that there are multiple app domains for every plugin, so even static variables stored in such plugin can have multiple values, which you cannot rely on. There is no documentation on MSDN that would explain how the sanboxing works (especially app domains in this case), but certainly using static variables is not a good idea.Of course if you are dealing with online, you cannot be sure if there is only single front-end server or many of them (which will also result in such behaviour)
Class level variables should be limited to configuration information. Using a class level variable as you are doing is not supported. In CRM Online, because of multiple web front ends, a specific request may be executed on a different server by a different instance of the plugin class than another request. Overall, assume CRM is stateless and that unless persisted and retrieved nothing should be assumed to be continuous between plugin executions.
Per the SDK:
The plug-in's Execute method should be written to be stateless because
the constructor is not called for every invocation of the plug-in.
Also, multiple system threads could execute the plug-in at the same
time. All per invocation state information is stored in the context,
so you should not use global variables or attempt to store any data in
member variables for use during the next plug-in invocation unless
that data was obtained from the configuration parameter provided to
the constructor.
Reference: https://msdn.microsoft.com/en-us/library/gg328263.aspx
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
Am modifying the complex function which is already written where they are using the below code :
private List<string> Values()
{
if (ViewBag.Sample == null)
{
ViewBag.Sample = TestData();
}
}
// where TestData() hits the DB and returns corresponding result
Values() is called multiple places in the same file where this will return by hitting the DB TestData() first time and from next calls it will directly return from ViewBag.
Is this is a good approach ?
What are all the alternative approach we have in MVC to handle this scenario ?As DB hit is a costly call we need to use some other techniques.
Thanks
You could either keep your data in session like this:
Session['*your session key*'] = TestData();
And then retrieve it like this:
var myData = Session['*your session key*'] as YourObject //cast it to an object if you need to.
Or you could use caching:
System.Web.HttpRuntime.Cache[cacheKey] = TestData
And retrieving:
var myData =System.Web.HttpRuntime.Cache[cacheKey] as YourObject
That code should ensure that you only touch the database the first time the method is invoked.
If the same data is used on multiple pages you could also have a lot at the Cache- or Session class.
If size of the data retrieved from database is not very big then you can use Cache
Otherwise you can store data in Session as well.
You have the options to keep the data like Session, Cache.
[OutputCache(Duration = 60)] // Caches for 60 seconds
private List<string> Values()
{
if (ViewBag.Sample == null)
{
ViewBag.Sample = TestData();
}
}
MVC Model Binder
See Sample code
I have a ASP.NET MVC application with the following controller action:
public ActionResult Upload(HttpPostedFileBase fileData)
{
Dictionary<string, int> mappings = new Dictionary<string, int>();
Byte[] destination = new Byte[fileData.ContentLength];
fileData.InputStream.Position = 0;
fileData.InputStream.Read(destination, 0, fileData.ContentLength);
var returnValue = GUIDGenerator(destination);
mappings.Add(fileData.FileName, returnValue);
return Json(new { success = true });
}
Every time the user selects a file and uploads it, I want to add its name and GUID into a Dictionary type "mappings". But With every upload, the mappings re-initializes itself. How do I persists values in it?
if you really want it persistend and scalable you will have to do this in a database. But using the Session-object or other cachich-mechanisms may work for you as well.
BTW: moving the dictionary into the controller won't work as well because the controller is created everytime a method on it is called (yeah you can change this, but this is the default behaviour).
I would consider using a database.
I suppose you could make a static variable at the top of the class:
private static Dictionary<string, int> _mappings = new Dictionary<string, int>();
But that will only live with that session on the server that code ran in. You might be better off saving and reloading that data from a database.
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;
}
}