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