ASP.NET Cache - circumstances in which Remove("key") doesn't work? - asp.net

I have an ASP.NET application that caches some business objects. When a new object is saved, I call remove on the key to clear the objects. The new list should be lazy loaded the next time a user requests the data.
Except there is a problem with different views of the cache in different clients.
Two users are browsing the site
A new object is saved by user 1 and the cache is removed
User 1 sees the up to date view of the data
User 2 is also using the site but does not for some reason see the new cached data after user 1 has saved a new object - they continue to see the old list
This is a shortened version of the code:
public static JobCollection JobList
{
get
{
if (HttpRuntime.Cache["JobList"] == null)
{
GetAndCacheJobList();
}
return (JobCollection)HttpRuntime.Cache["JobList"];
}
}
private static void GetAndCacheJobList()
{
using (DataContext context = new DataContext(ConnectionUtil.ConnectionString))
{
var query = from j in context.JobEntities
select j;
JobCollection c = new JobCollection();
foreach (JobEntity i in query)
{
Job newJob = new Job();
....
c.Add(newJob);
}
HttpRuntime.Cache.Insert("JobList", c, null, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Default, null);
}
}
public static void SaveJob(Job job, IDbConnection connection)
{
using (DataContext context = new DataContext(connection))
{
JobEntity ent = new JobEntity();
...
context.JobEntities.InsertOnSubmit(ent);
context.SubmitChanges();
HttpRuntime.Cache.Remove("JobList");
}
}
Does anyone have any ideas why this might be happening?
Edit: I am using Linq2SQL to retreive the objects, though I am disposing of the context.

I would ask you to make sure you do not have multiple production servers for load balancing purpose. In that case you will have to user some external dependency architecture for invalidating/removing the cache items.

That's because you don't synchronize cache operations. You should lock on writing your List to the cache (possibly even get the list inside the lock) and on removing it from the cache also. Otherwise, even if reading and writing are synchronized, there's nothing to prevent storing the old List right after your call to Remove. Let me know if you need some code example.

I would also check, if you haven't already, that the old data they're seeing hasn't been somehow cached in ViewState.

You have to make sure that User 2 sent a new request. Maybe the content it saws is from it's browser's cache, not the cache from your server

Related

WCF Transaction with multiple inserts

When creating a user, entries are required in multiple tables. I am trying to create a transaction that creates a new entry into one table and then pass the new entityid into the parent table and so on. The error I am getting is
The transaction manager has disabled its support for remote/network
transactions. (Exception from HRESULT: 0x8004D024)
I believe this is caused by creating multiple connections within a single TransactionScope, but I am unsure on what the best/most efficient way of doing this is.
[OperationBehavior(TransactionScopeRequired = true)]
public int CreateUser(CreateUserData createData)
{
// Create a new family group and get the ID
var familyGroupId = createData.FamilyGroupId ?? CreateFamilyGroup();
// Create the APUser and get the Id
var apUserId = CreateAPUser(createData.UserId, familyGroupId);
// Create the institution user and get the Id
var institutionUserId = CreateInsUser(apUserId, createData.AlternateId, createData.InstitutionId);
// Create the investigator group user and return the Id
return AddUserToGroup(createData.InvestigatorGroupId, institutionUserId);
}
This is an example of one of the function calls, all the other ones follow the same format
public int CreateFamilyGroup(string familyGroupName)
{
var familyRepo = _FamilyRepo ?? new FamilyGroupRepository();
var familyGroup = new FamilyGroup() {CreationDate = DateTime.Now};
return familyRepo.AddFamilyGroup(familyGroup);
}
And the repository call for this is as follows
public int AddFamilyGroup(FamilyGroup familyGroup)
{
using (var context = new GameDbContext())
{
var newGroup = context.FamilyGroups.Add(familyGroup);
context.SaveChanges();
return newGroup.FamilyGroupId;
}
}
I believe this is caused by creating multiple connections within a single TransactionScope
Yes, that is the problem. It does not really matter how you avoid that as long you avoid it. A common thing to do is to have one connection and one EF context per WCF request. You need to find a way to pass that EF context along.
The method AddFamilyGroup illustrates a common anti-pattern with EF: You are using EF as a CRUD facility. It's supposed to me more like a live object graph connected to the database. The entire WCF request should share the same EF context. If you move in that direction the problem goes away.

New item added to session on every request

I found this behaviour by accident, as I return the count of items in a session in an error message and found that some sessions had as many as 120 items in them (they should have 1!). On further investigation I found that every request seems to add an item into the session. They are all negative integers, like -710, -140 -528. I can't seem to see a pattern in what number comes up.
I have checked my code for any interactions with the Session object and as far as I can tell it is not me. I store one item in the session which is my own object which has a number of other properties on it. My session state is SQL server, and I am only serialising a certain set of values that need to be kept.
Has anyone seen anything like this or has any advice on where I can troubleshoot further?
Thank you in advance.
-- Edit, as requested - first where I count the items in the session - this is done in the page load event of my master page. I loop through so I could inspect using the debugger.
int itemCount = Session.Count;
for (int i = 0; i < itemCount; i++)
{
object o = Session[i];
}
-- here is where I add my custom object to the session. This is called at session start and in my master page. It runs on a "get, but if not there, create" principle.
HttpSessionState Session = HttpContext.Current.Session;
HttpRequest Request = HttpContext.Current.Request;
if (Session == null)
return null;
SessionData sessionData = (SessionData)Session[StaticNames.SESSION_NAME];
if (sessionData == null)
{
sessionData = new SessionData();
Session.Add(StaticNames.SESSION_NAME, sessionData);
}
I also have this to get the SessionData object from the session:
public SessionData(SerializationInfo info, StreamingContext ctxt)
{
this.IsManualLogin = (bool)info.GetValue("IsManualLogin", typeof(bool));
this.HasAskedUserForLocation = (bool)info.GetValue("HasAskedUserForLocation", typeof(bool));
// ... etc, more items for all users here
int? loginID = null;
try
{
loginID = info.GetInt32("LoginID");
}
catch
{
return;
}
this.LoginID = loginID.Value;
// ... etc, more items for logged in users only
}
There is also an equivalent method for adding this data to the SerializationInfo used for SqlSessionState.
Credit to the modest jadarnel27.
It turns out the Ajax Control Toolkit NoBot control adds an integer into your session on every request. My website has an auto 40 second refresh, similar to facebook, so this probably would have brought the whole thing crashing down at some point and I am lucky to find it now. Should anyone else consider using the NoBot control, be warned about this behaviour!

Need to setup SqlDependency per-user

System Scope
I have a database with a lot of users (over 50,000). At any time there may be 100-200 people logged in and actively using the system. The system is ASP.NET MVC 4, with Sql Server 2008 backend. For data access we are using Dapper.
Requirements
I am trying to build a notification component that has the following attributes:
When a new record is created in the [dbo.Assignment] table (with OwnerId = [Currently logged in user]), I need to update the Cache inside of an asp.net application.
I don't want to receive any notifications for users who are not actively online, as this would be a massive waste of resources)
Specific Questions:
Should I be using SqlDependency, SqlCacheDependency, or SqlNotification?
Assuming that we are using SqlDependency, how would I remove the Dependency.OnChange handler when user has logged out.
Any code samples would be much appreciated, as this has consumed the whole part of my day trying to figure it out.
Here is the current code
public IList<Notification> GetNotifications(string userName)
{
Cache o = HttpContext.Current.Cache;
if (o["Notifications_" + userName] == null)
{
var notifications = new List<Notification>();
using (var cn = new SqlConnection(getSQLString()))
{
using (var cmd = cn.CreateCommand())
{
var parameter = new SqlParameter("Employee_Cd", SqlDbType.Char, 30) { Value = userName };
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "Notifications.Assignments";
cmd.Parameters.Add(parameter);
cmd.Notification = null;
var dependency = new SqlCacheDependency(cmd);
cn.Open();
using (var dr = cmd.ExecuteReader())
{
// this is where you build your cache
while (dr.Read())
{
var obj = new Notification();
obj.Name = dr["Name"].ToString();
notifications.Add(obj);
}
dr.Close();
}
HttpContext.Current.Cache.Insert("Notifications_" + userName,
notifications,
dependency,
DateTime.Now.AddDays(1D),
Cache.NoSlidingExpiration);
}
}
}
return (List<Notification>) o["Notifications_" + userName];
}
Note: I am not experienced with using SqlDependencies, as I have never really needed to use them until today. It's very possible that I am overlooking something important.
I didn’t really use any of these techniques but here are some alternatives that you can create yourself that will do the job just as good.
If you need to update cache every time new record is inserted into dbo.Assignment table why not create OnInserted event in your data access layer that will notify the cache object to refresh?
Another thing you can to is to create INSERT trigger in Assignemt table and another table that can look like this dbo.Cache (LastUpdate datetime). Trigger will insert value into Cache table and your application cache can ping this table like every X mins or X seconds to see if cache update is required.
If you need to refresh the cache immediately after record is inserted triggers might be an overkill because you’d have to ping Cache table probably every second but if you have 200 online users at a time that probably won’t make much of a difference in DB performance.
There is a lot of work if you want to implement these for a lot of tables but since this is only one table this might turn out to be faster way than implementing built in cache mechanisms.

Cache expires before it supposed to

Can someone help me here? I have following code to store and revieve catch, however, it doesn't work. The cache expires in mins even I set it to 14 days in slidingExpiration. Thanks in advance!
public static List<ReplyDTO> VideoCommentList()
{
List<ReplyDTO> replyList = new List<ReplyDTO>();
if (HttpRuntime.Cache["videoComment"] == null)
{
HttpRuntime.Cache.Remove("videoComment");
HttpRuntime.Cache.Insert("videoComment", replyList, null, Cache.NoAbsoluteExpiration, TimeSpan.FromDays(14));
}
else
{
replyList = (List<ReplyDTO>)HttpRuntime.Cache["videoComment"];
}
if (replyList.Count > 8)
{
replyList = replyList.OrderByDescending(x => x.DateCreated).Take(8).ToList();
}
else
{
replyList = replyList.OrderByDescending(x => x.DateCreated).ToList();
}
return replyList;
}
public static List<ReplyDTO> AddVideoComment(ReplyDTO replyDTO)
{
List<ReplyDTO> replyList = new List<ReplyDTO>();
replyList = VideoCommentList();
replyList.Add(replyDTO);
HttpRuntime.Cache.Insert("videoComment", replyList, null, Cache.NoAbsoluteExpiration, TimeSpan.FromDays(14));
if (replyList.Count > 8)
{
replyList = replyList.OrderByDescending(x => x.DateCreated).Take(8).ToList();
}
else
{
replyList = replyList.OrderByDescending(x => x.DateCreated).ToList();
}
return replyList;
}
ASP.net cache is in-memory, so if your IIS process or application pool recycles it will get clear. You can check following things which can cause recycling of process
If you modify web.config, IIS shutdown the old instance and slowly transfer the traffic to a new instance, in this process in-memory is recycled. How to check this: You can detect this situation by checking the AppDomain.IsFinalizingForUnload and logging that during the callback.
Application Pool Recycling: There is a configuration in IIS, according to which if IIS process is idle for a specified time, it recycles it. You can check this on server, and increase this time or disable the recycling altogether.
Every process has limitation on how much memory it can consume, if you are adding too many objects in memory, it will increase the memory consumption of IIS, and in critical time OS will recycle the process.
EDIT
In your program you are adding replyList item to cache and then doing .Take() operation. As replyList is reference object, if you modify it, it will get updated in the cache also. So if in your program, if you do replyList == null it will update the item in cache.
So modify your code like this and try
public static List<ReplyDTO> VideoCommentList()
{
List<ReplyDTO> replyList = new List<ReplyDTO>();
if (HttpRuntime.Cache["videoComment"] == null)
{
//Call to .Remove is not required
//HttpRuntime.Cache.Remove("videoComment");
HttpRuntime.Cache.Insert("videoComment", replyList, null,
Cache.NoAbsoluteExpiration, TimeSpan.FromDays(14));
}
else
{
//No need to check count > 8, Take will handle it for you
replyList = ((List<ReplyDTO>)HttpRuntime.Cache["videoComment"])
.OrderByDescending(x => x.DateCreated)
.Take(8).ToList();
}
return replyList;
}
public static List<ReplyDTO> AddVideoComment(ReplyDTO replyDTO)
{
//Read from cache
List<ReplyDTO> replyList = ((List<ReplyDTO>)HttpRuntime.Cache["videoComment"]);
if(replyList == null)
replyList = VideoCommentList();
replyList.Add(replyDTO);
HttpRuntime.Cache.Insert("videoComment", replyList, null, Cache.NoAbsoluteExpiration, TimeSpan.FromDays(14));
//Here you are creating a new list, and not referencing the one in the cache
return replyList.OrderByDescending(x => x.DateCreated).Take(8).ToList();
}
IMPORTANT SUGGESTION
If you want to check when and why your object is removed from the cache, you can take help of CacheItemRemovedCallback option on the insertion. Using this and CacheItemRemovedReason argument, you can log the reason of object removal from cache. Reasons
Removed - Your code has removed the item from cache by calling Insert or Remove method.
Expired - The item is removed from the cache because it expired.
Underused - When system run low on memory, it freed up memory by removing item from the cache.
DependencyChanged - The item is removed from the cache because the cache dependency associated with it changed. (In your case it is not valid)
Hope this information helps you.
In order to track down WHY your item is being removed from cache, I'd recommend using a different overload of the HttpRuntime.Cache.Insert method that allows you to specify a CacheItemRemovedCallback callback function.
Cache.Insert Method (String, Object, CacheDependency, DateTime, TimeSpan, CacheItemPriority, CacheItemRemovedCallback)
Aside from that your caching code seems good. But once you change your code to specify a callback, log the ejection reason and that will probably give you a better understanding of why your cached item is getting clear.
Like most of the other answers, I suspect that your app is getting recycled/reset for any number of reasons. I think that most apps on a production machine recycle at least once a day, especially in a shared hosting environment. So I'd guess that your data will stay cached for a day at most.
The cache is in-memory and will expire when your application recycles. I'm guessing you're evaluating this on a development machine where either the low amount of traffic, or your file edits causes the application to recycle.
Your cache objects can get trimmed due to multiple reasons...
AppDomain recycle.
Memory pressure.
Crash.
Use of Web Garden.
Load balancing.
and so on...
This post should clarify a bit more...
http://blogs.msdn.com/b/praveeny/archive/2006/12/11/asp-net-2-0-cache-objects-get-trimmed-when-you-have-low-available-memory.aspx
In the AddVideoComment() method , change the cache item insert lines to:
HttpRuntime.Cache.Insert("videoComment", replyList, null, Cache.NoAbsoluteExpiration, TimeSpan.FromDays(14),CacheItemPriority.NotRemovable,null);
And in the VideoCommentList() method, use:
if (HttpRuntime.Cache["videoComment"] == null)
{
replyList = VideoCommentList();
HttpRuntime.Cache.Insert("videoComment", replyList, null, Cache.NoAbsoluteExpiration, TimeSpan.FromDays(14),CacheItemPriority.NotRemovable,null);
}
No need of using HttpRuntime.Cache.Remove("videoComment"); as HttpRuntime.Cache.Insert( will replace the existing cache item.
Cheers,
DeveloperConcord

Newly Created Session doesn't retain session contents

The system I am working on does not use standard ASP.NET Auth/ Membership facilities for logging users in/ out. Therefore after logging the user in I want to issue a new Session ID to the user in order to prevent Session trapping/ Hijacking. The problem i have is that although I have been able to successfully create a new session with a new ID and copy the various components to the newly created session eg. session["value"]. By the end of the code excerpt below the newly created session is the current HTTPContext's session, and has the session values that were copied accross. However after performing a Response.Redirect the new session is in action, but none of the session["values"] have persisted across the two requests. As you can see from the code below i've tried adding the values to a number of collections to avail.
Any help would be amazing!! Thanks in advance
bool IsAdded = false;
bool IsRedirect = false;
HttpSessionState state = HttpContext.Current.Session;
SessionIDManager manager = new SessionIDManager();
HttpStaticObjectsCollection staticObjects = SessionStateUtility.GetSessionStaticObjects(HttpContext.Current);
SessionStateItemCollection items = new SessionStateItemCollection();
foreach (string item in HttpContext.Current.Session.Contents)
{
var a = HttpContext.Current.Session.Contents[item];
items[item] = a;
}
HttpSessionStateContainer newSession = new HttpSessionStateContainer(
manager.CreateSessionID(HttpContext.Current),
items,
staticObjects,
state.Timeout,
true,
state.CookieMode,
state.Mode,
state.IsReadOnly);
foreach (string item in HttpContext.Current.Session.Contents)
{
var a = HttpContext.Current.Session.Contents[item];
newSession.Add(item,a);
}
SessionStateUtility.RemoveHttpSessionStateFromContext(HttpContext.Current);
SessionStateUtility.AddHttpSessionStateToContext(HttpContext.Current, newSession);
manager.RemoveSessionID(HttpContext.Current);
manager.SaveSessionID(HttpContext.Current, newSession.SessionID, out IsRedirect, out IsAdded);
return newSession.SessionID;
Maybe I'm missing something here but won't this work:
Session["mysession"] = mySessionObject;
Basically it appears it's not possible since you can only add session variables once there has been one round trip to the client to create the corresponding session cookie. Therefore I had to create the new new session (with new ID) so that by the time I came to adding session variables, the client cookie had the appropriate session id: annoying since this in reality is issuing the new session ID before the user is authenticated.
Interestingly, it seems a little strange that issuing a new Session ID is exactly what the standard asp.net authentication/ membership functionality does but is able to maintain session variables, and yet doing it manually it doesn't....are there some methods for this that are not being exposed to us mere developers maybe....

Resources