HttpContext Cache attributes don't work well with MVC plumbing. They work for a page or public controller methods. If you want to load multiple content from different sources without using ajax or without making multiple calls, you need to use cache calls directly.
There are many third party tools for caching. You are not restricted to HttpContext.
This example will show you how to load data into cache and update that only if we need to. This example will use table as a source and add triggers to track changes on the table.
We need both update and delete triggers in our data source to track changes. I provided scripts here and included source code for simple mvc and EF code first application at github for this sample. You can check that for cache usage and code first usage for stored procs.
Lets create our dummy source table:
------------------------------------------------------------------
There are many third party tools for caching. You are not restricted to HttpContext.
This example will show you how to load data into cache and update that only if we need to. This example will use table as a source and add triggers to track changes on the table.
We need both update and delete triggers in our data source to track changes. I provided scripts here and included source code for simple mvc and EF code first application at github for this sample. You can check that for cache usage and code first usage for stored procs.
Lets create our dummy source table:
------------------------------------------------------------------
USE [MarketDb] GO /****** Object: Table [dbo].[MarketEvents] Script Date: 04/16/2012 22:08:43 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TABLE [dbo].[MarketEvents]( [MarketEventId] [int] IDENTITY(1,1) NOT NULL, [CompanyId] [int] NOT NULL, [Campaign] [nvarchar](50) NOT NULL, [CampaignType] [nvarchar](50) NOT NULL, [Description] [nvarchar](50) NULL, [StartDate] [datetime] NULL, [EndDate] [datetime] NULL, [RowGuid] [uniqueidentifier] NOT NULL, [EventStatus] [nvarchar](50) NOT NULL, PRIMARY KEY CLUSTERED ( [MarketEventId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
We are targetting campaign column as a source. We will add one table to track all cache updates. It will store one record for each table. When we make change to data, this record will keep update time. We will compare our cache time to this to decide if we need to load fresh data.
CREATE TABLE [dbo].[CacheTrack]( [CacheTrackId] [int] IDENTITY(1,1) NOT NULL primary key, [TableName] [nvarchar](50) NOT NULL, [UpdateTime] [datetime] NOT NULL, )
Triggers to update changes at our column: DELETE TRIGGER
create TRIGGER [dbo].[dMarketEventsCode] ON [dbo].[MarketEvents]
AFTER delete AS
BEGIN DECLARE @Count int; --=================================================== -- After Trigger for delete -- Trigger type is after so this will happen after all update operation is done. --=================================================== SET @Count = @@ROWCOUNT; IF @Count = 0 RETURN; SET NOCOUNT ON; BEGIN TRY -- Insert record into CacheTrack if not exists, otherwise update that If NOt exists (select CacheTrackId from CacheTrack where tablename = 'MarketEvents') INSERT INTO dbo.CacheTrack (TableName ,UpdateTime ) SELECT 'MarketEvents' ,Getutcdate() ; ELSE UPDATE dbo.CacheTrack SET UpdateTime = Getutcdate() where TableName = 'MarketEvents'; END TRY BEGIN CATCH EXECUTE [dbo].[uspPrintError]; -- Rollback any active or uncommittable transactions before -- inserting information in the ErrorLog IF @@TRANCOUNT > 0 BEGIN ROLLBACK TRANSACTION; END EXECUTE [dbo].[uspLogError]; END CATCH; END
------UPDATE TRIGGER
CREATE TRIGGER [dbo].[uMarketEventsCode] ON [dbo].[MarketEvents] AFTER UPDATE,INSERT AS BEGIN DECLARE @Count int; --=================================================== -- After Trigger to detect changes for Campaign. we need to detect those to populate cache -- Trigger type is after so this will happen after all update operation is done. --=================================================== SET @Count = @@ROWCOUNT; IF @Count = 0 RETURN; SET NOCOUNT ON; print 'uMarketEventsCode fired'; BEGIN TRY --DEBUG ----select 'inserted',* from inserted ----select 'deleted',* from deleted ----select i.Campaign FROM Inserted i ---- LEFT JOIN Deleted d ON i.MarketEventId = d.MarketEventId ---- WHERE ISNULL(i.Campaign, '<some null value>') != ISNULL(d.Campaign, '<some null value>') --Update only if column data changes --Update function does not care if column data changed or not,It returns true if statement contains this column --We can check previous value to make sure it is really a change on this column. If it is an insert op, it does not have deleted record --Dont forget that trigger's inserted or deleted may contain multiple rows. It fires for all the inserts,updates.. IF UPDATE(Campaign) and exists(select i.Campaign FROM Inserted i LEFT JOIN Deleted d ON i.MarketEventId = d.MarketEventId WHERE ISNULL(i.Campaign, '<some null value>') != ISNULL(d.Campaign, '<some null value>')) -- Insert record into CacheTrack if not exists, otherwise update that BEGIN print 'uMarketEventsCode add record'; If NOt exists (select CacheTrackId from CacheTrack where tablename = 'MarketEvents') INSERT INTO dbo.CacheTrack (TableName ,UpdateTime ) SELECT 'MarketEvents' ,Getutcdate() ; ELSE UPDATE dbo.CacheTrack SET UpdateTime = Getutcdate() where TableName = 'MarketEvents'; END; END TRY BEGIN CATCH EXECUTE [dbo].[uspPrintError]; -- Rollback any active or uncommittable transactions before -- inserting information in the ErrorLog IF @@TRANCOUNT > 0 BEGIN ROLLBACK TRANSACTION; END EXECUTE [dbo].[uspLogError]; END CATCH; END;
This example shows dynamic cache in MVC.
public ActionResult Index() { ViewBag.Message = "Welcome to ASP.NET MVC!"; int activecompanyid = 1; //this will load from cache and use it until we need to update the cache ViewBag.Mailcodes = GetCacheMarketCodes(activecompanyid); return View(); } public const string RefreshKeyword = "refreshed"; /// <summary> /// we will cache market codes /// cache will be updated if something changes from website or db. /// Db triggers handle changes and record in a simple table. We check that /// every time,so we can skip expensive queries and only check audit tables to refresh cache. /// </summary> /// <returns></returns> private string[] GetCacheMarketCodes(int companyid) { //keep update flag for mailcodes. we will update this when some change happens at db string updateflag = (string)HttpContext.Cache["MailCodesUpdate" + companyid]; bool needsupdate = CheckTriggerRecordsForUpdate("MarketEvents", updateflag); if (needsupdate || HttpContext.Cache["MailCodes" + companyid] == null) { using (var db = new MarketContext(ConnectionStr)) { //make call to db to pget items var listitems = (from x in db.MarketEvents orderby x.Campaign select x.Campaign).Distinct().OrderBy(a => a). ToArray(); //Add item to cache with expiration time, sliding, priority and callback if needed HttpContext.Cache["MailCodes" + companyid] = listitems; //set as refreshed to track when we refreshed this HttpContext.Cache["MailCodesUpdate" + companyid] = RefreshKeyword + DateTime.UtcNow.ToString(); } } return HttpContext.Cache["MailCodes" + companyid] as string[]; } private bool CheckTriggerRecordsForUpdate(string tablename, string updateflag) { if (updateflag.HasSomething() && updateflag.Contains(RefreshKeyword)) { using (var db = new MarketContext(ConnectionStr)) { var entry = db.ProcGetCacheUpdateTime(tablename).FirstOrDefault(); //now check if we have something in cache records if (entry != null) { DateTime lastupdate = DateTime.Parse(updateflag.Replace(RefreshKeyword, "")); //compare if (lastupdate >= entry.UpdateTime) { //dont need update. our cache is better than what triggered in db. return false; } } else { return false; //no need to refresh that. we dont have any entry in cache table } } } return true; }
Link to Github code: https://github.com/omercs/CacheMvc
No comments:
Post a Comment
Hey!
Let me know what you think?