Working on the data access / model layer in this little MVC2 project and trying to think things out to future projects.
I have a database with some basic tables and I have classes in the model layer that represent them. I obviously need something to connect the two. The easiest is to provide some sort of 'provider' that can run operations on the database and return objects.
But this is for a website that would potentially be used "a lot" (I know, very general) so I want to cache results from the data layer and keep the cache updated as new data is generated.
This question deals with how best to approach this problem of dual DALS. One that returns cached data when possible and goes to the data layer when there is a cache miss. But more importantly, how to integrate the core provider (thing that goes into database) with the caching layer so that it too can rely on cached objects rather than creating new ones.
Right now I have the following interfaces:
IDataProvider
is used to reach the database. It doesn't concern itself with the meaning of the objects it produces, but simply the way to produce them.
interface IDataProvider{
// Select, Update, Create, et cetera access
IEnumerable<Entry> GetEntries();
Entry GetEntryById(int id);
}
IDataManager
is a layer that sits on top of the IDataProvider
layer and manages the cache
interface IDataManager : IDataProvider{
void ClearCache();
}
Note that in practice the IDataManager
i开发者_如何学JAVAmplementation will have useful helper functions to add objects to their related cache stores. (In the future I may define other functions on the interface)
I guess what I am looking for is the best way to approach a loop back from the IDataProvider
implementations so that they can access the cache. Or a different approach entirely may be in order? I am not very interested in 3rd party products at the moment as I am interested in the design of these things much more than this specific implementation.
Edit: I realize the title may be a bit misleading. I apologize for that... not sure what to call this question.
Personally, I currently use NHibernate which handles caching for you. In the past, I have done something similar to what you are trying to do, however I never had need to separate the caching layer from the dao layer. Is there some reason that you need to separate these? I suppose it would make sense if you have significantly different caching needs dependent on the object type. Anyway, assuming that it is necessary, here is how I would go about doing it based on past experience. Firstly, lets assume we have the following classes: PersistentObject, which is a base object that has at least an integer property called "ID"
public class PersistentObject
{
public int ID { get; set; }
}
Now say you have a sample type that you would like to persist:
public class SampleObject : PersistentObject
{
public string SomeValue { get; set; }
}
In place of "DataProvider", I have a simple repository interface:
public interface IRepository<T> where T : PersistentObject
{
T Get(int id);
void Save(T e);
void Delete(T e);
}
How about, instead of your IDataManager interface, you have an abstract class that you can implement that is a repository that handles caching (I have my own "CacheManager" class that I'm using here, it could be implemented any number of ways):
public abstract class CacheRepository<T> : IRepository<T> where T : PersistentObject
{
private const string CacheKeyPrefix = "RepoCache-";
private string GetCacheKey(int id)
{
return CacheKeyPrefix + typeof(T).FullName + "-" + id.ToString();
}
public T Get(int id)
{
string cacheKey = GetCacheKey(id);
T obj = CacheManager.GetItemFromCache<T>(cacheKey);
if (obj == null)
{
obj = this.GetData(id);
if (obj != null)
CacheManager.AddItemToCache(obj, cacheKey);
}
return obj;
}
public void Save(T obj)
{
string cacheKey = GetCacheKey(obj.ID);
this.SaveData(obj);
CacheManager.AddItemToCache(obj, cacheKey);
}
public void Delete(T obj)
{
string cacheKey = GetCacheKey(obj.ID);
this.DeleteData(obj);
CacheManager.RemoveItemFromCache(cacheKey);
}
protected abstract T GetData(int id);
protected abstract void SaveData(T obj);
protected abstract void DeleteData(T obj);
}
You'll notice that the only members that are publicly exposed are the methods that implement IRepository. There are 3 abstract methods that must be implemented for data access by the individual repository. So we can do that here for our SampleObject:
public class SampleObjectRepository : CacheRepository<SampleObject>
{
protected override SampleObject GetData(int id)
{
//do some loading
return new SampleObject();
}
protected override void SaveData(SampleObject obj)
{
//do some saving
}
protected override void DeleteData(SampleObject obj)
{
//do some deleting
}
}
Lastly, this is how you would go about utilizing this code:
public class UsageSample
{
public UsageSample()
{
//save a new object
SampleObjectRepository repo = new SampleObjectRepository();
SampleObject sampleObj = new SampleObject();
repo.Save(sampleObj);
//load an object by ID
int id = sampleObj.ID;
sampleObj = repo.Get(id);
//delete an object
repo.Delete(sampleObj);
}
}
It gets significantly more complex when you start to talk about returning collections and querying, but this is at least a simplistic start. Are you sure you're not interested in 3rd party solutions? :-) NHibernate does a pretty great job of handling all of this for you.
精彩评论