Welcome to the CodeSmith Community!

Re: Your Comments - Robert / Ben / John / Bdiaz ? - A Caching Implementation in the DAL/BLL. (With Code Sample)

.netTiers

A description has not yet been added to this group.

Your Comments - Robert / Ben / John / Bdiaz ? - A Caching Implementation in the DAL/BLL. (With Code Sample)

  • rated by 0 users
  • This post has 2 Replies |
  • 2 Followers
  • I was trying to implement caching into nettiers. Entity DS Caching is good - but i am working on an Data Layer which is accessed by a winforms app as well as a web app. My aim was to re-use the cache for both the winformsapp/web svc and the website.
            
    So i'd like to get all data in the cache - not make calls unless their is a change. And if their is a change - both the caches are invalidated. 

     
    The general code architecture is like this -

    GET CALLs
    1. Check if Caching in enabled for this entity.
        A. Check if data is present in cache - if yes return in from here
        B. Else make a DB call and store the stuff in  cache

    UPDATE/DETELE/INSERT Calls
    1. Check IF Caching in ENabled for this entity
        A. Delete all relevant data from the cache.

    My architecture for caching is based on the book -
    http://www.amazon.com/ASP-NET-2-0-Website-Programming-Programmer/dp/0764584642
    ASP.NET 2.0 Website Programming: Problem - Design - Solution (Programmer to Programmer)

    Source code for the solution developed in the book is available here -
    http://www.codeplex.com/Wiki/View.aspx?ProjectName=TheBeerHouse


    I understand that you maybe not need caching for all entitites(MOSTLY WRITE). So i have a partial class which is only generated the first time and     
    in which you can decide the entities for which you want to enable caching.         


    I debated and researched a lot about this - and initial results on my production server have been pretty good. However I do understand this is not a well architectured solution. I would ideally like to do this in the BLL rather than the DAL. And it doesnt cache when there is a paging call. 

    And i am still unsure if this interferes with the ent lib caching thats inbuilt into nettiers. What are your suggestions thoughts big guys ? Robert ? Ben ? John ?  Bdiaz ?

    Am i on the right line ? Or setting myself up for trouble later ? 

     Do you think this would be useful for many people to be incorporated into Nettiers ?

           I made some changes in the templates .
            Here is some sample code i generated yesterday to test it out -
            
      

     -----------------

     

         public override Projects123.BLL.TList<Contacts> GetByFkClientID(TransactionManager transactionManager, System.Int32? fkClientID, int start, int pageLength, out int count)
            {
                Projects123.BLL.TList<Contacts> tmp = new Projects123.BLL.TList<Contacts>();
                

                string cacheKey;
                cacheKey = "Contacts_FkClientID_" + fkClientID.ToString();
                

                //Check if caching is enabled for this entity
                if (EnableFkClientIDCaching && pageLength > 100)
                {
            
                // Check if we have this Projects123.BLL.TList<Contacts> information in the cache
                object currentCacheObject = System.Web.HttpRuntime.Cache[cacheKey];
                if (currentCacheObject != null)
                {
                    count = 1;
                    return (Projects123.BLL.TList<Contacts>)currentCacheObject;
                }

                //Check if have the Contacts collection in memory
                object allCacheObject = System.Web.HttpRuntime.Cache[_AllCacheKey];
                if (allCacheObject != null)
                {
                    tmp = (Projects123.BLL.TList<Contacts>)allCacheObject;

                    Projects123.BLL.TList<Contacts> singleEntity = new Projects123.BLL.TList<Contacts>();
                    
                        singleEntity = tmp.FindAll("fkClientID", fkClientID);

                    // Was this Contacts present in the collection ?
                    if (singleEntity != null)
                    {
                        System.Web.HttpRuntime.Cache.Insert(cacheKey, singleEntity);
                        count = 1;
                        return singleEntity;
                    }else{
                        tmp.Clear();
                    }
                }
                }
            

                
                
                SqlDatabase database = new SqlDatabase(this._connectionString);
                DbCommand commandWrapper = StoredProcedureProvider.GetCommandWrapper(database, "dbo.tblContacts_GetByFkClientID", _useStoredProcedure);
                
                    database.AddInParameter(commandWrapper, "@FkClientID", DbType.Int32, fkClientID);
                
                IDataReader reader = null;
            
                try
                {
                    if (transactionManager != null)
                    {
                        reader = Utility.ExecuteReader(transactionManager, commandWrapper);
                    }
                    else
                    {
                        reader = Utility.ExecuteReader(database, commandWrapper);
                    }        
            
                    //Create collection and fill
                    Fill(reader, tmp, start, pageLength);
                    count = -1;
                    if(reader.NextResult())
                    {
                        if(reader.Read())
                        {
                            count = reader.GetInt32(0);
                        }
                    }
                }
                finally
                {
                    if (reader != null)
                        reader.Close();
                }
                
                if (tmp.Count >= 1)
                {
                    //There is going to be a collection returned
                    if (EnableFkClientIDCaching && pageLength > 100)
                    {
                      // Add this to cache to avoid call the next time.
                    System.Web.HttpRuntime.Cache.Insert(cacheKey, tmp);
                    }
                    return tmp;
                }

                else if (tmp.Count == 0)
                {
                    return tmp;
                }
                else
                {
                    throw new DataException("Cannot find the unique instance of the class.");
                }
                
                //return rows;
            }
            
            
        ----------------------
      
            
        public override bool Update(TransactionManager transactionManager, Projects123.BLL.Contacts entity)
            {
                SqlDatabase database = new SqlDatabase(this._connectionString);
                DbCommand commandWrapper = StoredProcedureProvider.GetCommandWrapper(database, "dbo.tblContacts_Update", _useStoredProcedure);
                
                database.AddInParameter(commandWrapper, "@PkContactID", DbType.Int32, entity.PkContactID );
                database.AddInParameter(commandWrapper, "@FkClientID", DbType.Int32, (entity.FkClientID.HasValue ? (object) entity.FkClientID : System.DBNull.Value) );
                database.AddInParameter(commandWrapper, "@IsClientContact", DbType.Boolean, entity.IsClientContact );
                database.AddInParameter(commandWrapper, "@FirstName", DbType.AnsiString, entity.FirstName );
                database.AddInParameter(commandWrapper, "@LastName", DbType.AnsiString, entity.LastName );
                database.AddInParameter(commandWrapper, "@MI", DbType.AnsiString, entity.MI );
                database.AddInParameter(commandWrapper, "@Address1", DbType.AnsiString, entity.Address1 );
                database.AddInParameter(commandWrapper, "@Address2", DbType.AnsiString, entity.Address2 );
                database.AddInParameter(commandWrapper, "@City", DbType.AnsiString, entity.City );
                database.AddInParameter(commandWrapper, "@State", DbType.AnsiString, entity.State );
                database.AddInParameter(commandWrapper, "@Zip", DbType.AnsiString, entity.Zip );
                database.AddInParameter(commandWrapper, "@BusinessPhone", DbType.AnsiString, entity.BusinessPhone );
                database.AddInParameter(commandWrapper, "@HomePhone", DbType.AnsiString, entity.HomePhone );
                database.AddInParameter(commandWrapper, "@MobilePhone", DbType.AnsiString, entity.MobilePhone );
                database.AddInParameter(commandWrapper, "@ContactFax", DbType.AnsiString, entity.ContactFax );
                database.AddInParameter(commandWrapper, "@Email", DbType.AnsiString, entity.Email );
                database.AddInParameter(commandWrapper, "@HourlyRate", DbType.Currency, (entity.HourlyRate.HasValue ? (object) entity.HourlyRate : System.DBNull.Value) );
                database.AddInParameter(commandWrapper, "@TravelRate", DbType.Currency, (entity.TravelRate.HasValue ? (object) entity.TravelRate : System.DBNull.Value) );
                database.AddInParameter(commandWrapper, "@Active", DbType.Boolean, entity.Active );
                database.AddInParameter(commandWrapper, "@FkSubscriberID", DbType.Int32, entity.FkSubscriberID );
                
                int results = 0;
                
                
                if (transactionManager != null)
                {
                    results = Utility.ExecuteNonQuery(transactionManager, commandWrapper);
                }
                else
                {
                    results = Utility.ExecuteNonQuery(database,commandWrapper);
                }
                
                //Stop Tracking Now that it has been updated and persisted.
                if (DataRepository.Provider.EnableEntityTracking)
                    EntityManager.StopTracking(entity.EntityTrackingKey);
                
                
                entity.AcceptChanges();
                
                //Caching Checks BEGIN    
                if (EnableGetAllCaching)
                {
                    System.Web.HttpRuntime.Cache.Remove("Contacts_Contacts_All");
                }
                    
                RemoveCustomCache();
                

              
                if(EnablePkContactIDCaching)
                {
                    // If this is an Index    

                    //Primary Index
                    string cacheKey;
                    cacheKey = "Contacts_PkContactID_" + entity.PkContactID.ToString();
                    System.Web.HttpRuntime.Cache.Remove(cacheKey);
                
                }
            

              
                if(EnableFkClientIDCaching)
                {
                    // If this is an Index    
                    // This is an Index - but not the primary
                    string prefix;
                    prefix = "Contacts_FkClientID_" + entity.FkClientID.ToString() ;    
                    //DeleteFromCache(prefix);    
                    System.Web.HttpRuntime.Cache.Remove(prefix);
                
                }
            
            
                
                //Foreign Keys
                if(EnableFkSubscriberIDCaching)
                {
                    string prefix;
                    prefix = "Contacts_FkSubscriberID_" + entity.FkSubscriberID.ToString() ;    
                    System.Web.HttpRuntime.Cache.Remove(prefix);
                    //DeleteFromCache(prefix);
                }
                

                
                //End of Caching Checks
        
                return Convert.ToBoolean(results);
            }        
            
            

            
            // This class is not overwritten.
                public partial class SqlContactsProviderBase : ContactsProviderBase
        {
            #region Declarations
            
            private bool EnableGetAllCaching = true;
            private bool EnablePkContactIDCaching = true;
            
            private bool EnableFkClientIDCaching = false;
            
            
            private bool EnableFkSubscriberIDCaching = true;
                
            #endregion "Declarations"

            public void RemoveCustomCache()
            {
                string cacheKey;
                cacheKey = "VList<ContactNames>All";
                System.Web.HttpContext.Current.Cache.Remove(cacheKey);
            }

        
        }//end class

  • Hi,

    You're correct, I would not implement the cache in the DAL, but rather in your BLL, like a Service Or Domain layer.  Whereever the entry point to your behavior methods are kept.   If using Sql2005, you can also look into implementing a SQL Cache Dependancy, so you won't have to manage the cleanup of when an item has changed in the DB.  Also, it will be easier if the data was changed directly in the DB and not from your application.

    You can also use the EntLib cache, which is IMO a much more robust solution since you can have that much more control over your cache, and configure much of it in a configuration so that it can be changed later.  We wrote a light-weight wrapper to much of it in the Entities project, EntityCache.cs.  We also expose the EntLib library, so that you can go directly to the entlib cache.

     


    Robert Hinojosa
    -------------------------------------
    Member of the Codesmith Tools, .netTiers, teams
    http://www.nettiers.com
    -------------------------------------

  • 1. I want to use a lot of caching - for items which change a lot. MSDN says SQL Cache Dependency hurts performance and you should avoid it unless your items are MOSTLY READ. I dont expect any direct DB changes and all the clients - Windows and Web will ultimately go through the same DQL Client DAL.

     2. I understand architecturally it makes most sense to have caching in Biz layer - thats where i started. However - i needed one method and in the Biz there are multiple methods - calling the one DAL layer function. I thought it was the quickest way to test things out!

    3. Can you give me some quick intro to the Ent Lib Cache ? Getting started - i am sure its a better way of doing things ...

     

     

     

Page 1 of 1 (3 items)