给DAL层加上Cache
如何给DAL层加上Cache?通常关系型数据库中的一条记录对应一个实体,for example:User表中的一条记录对应User类的一个实例,就两个字段用户编号(ID)和姓名(Name)。好了,那么在DAL中我们通常有个根据用户编号得到User对象,
方法的通常如下:
Public User getObj(int id);
{
Return GetUserFormDB(id);
}
好了以上就是我们通常的写法,每次从数据库中去。
有时为了性能,减少访问数据库的次数,或者更确切的说是减少IO次数,希望从内存中取数据。现在给这个方法加上Cache如下:
Public User getObj(int id)
{
//代码1
if(IsExistCache(id)) //如果对象在缓存中
{
Return GetCache(id);//则从缓存中取得后返回
}
//代码2
User user = GetUserFormDB(id); //否则从数据库中取
//代码3
if(user!=null)
{
SetCache(user); //如果从数据库中取出的不为空,则加入缓存中。
}
Return user;
}
这个方法比之前的方法多了大概8行代码,所加的代码几乎可以成为一个模式,google一把您就知道。But,这仅仅是一个User类中的一个getObj方法(根据主键值得到对应的记录)。就写了8行,如果是100类个呢?1000个呢?10000个呢?即使你会说,咱们有CodeSmith,咱们是码农,咱们可以加班到凌晨2点。OK,那我再问:即使用CodeSmith可以生成了,那么我们通常在开发调试时都是不需要缓存的,它会干扰我们,我们需要直接访问数据库,而这里的缓存会妨碍我们的调试,但是现在没办法CodeSmith已经帮我们都生成好了,咋办?好,您说我们可以在系统上线之前再改,因为我们在DAL中可以写2个版本的方法(一个叫getobj,另一个叫getobj_Cache),开发时用前者,上线之前可以全局替换所有BLL中的getobj为getobj_Cache。好了,我们现在是在给自己挂一个坑,然后再往里跳。上面方法的问题出在访问数据库和访问Cache这两件事耦合到了一起,我们要把这两件事剥离开,我们只关心从数据库中取,而让跟缓存有关系的事情交给另一个对象去做。就像把上面的方法从中间切开,能实现这个功能的也就是AOP了,什么叫AOP,您google一把就知道,关于AOP的框架很多,在这里用Castle实现一把,大概意思是:按照以前的方式去写(从数据库中取就一行代码),把跟缓存有关的事都剥离出去,让一个对象去处理,这个对象一般叫拦截器,当我们调用getObj(id)方法,在执行其中的GetUserFormDB(id)方法前拦截器先拦截
并且执行代码1,然后执行代码2,接着再一次拦截并执行代码3,大概意思就这样,下面看下代码片段:
首先是一个DAL中的GetObj(id)方法:
[Cache("User",OperateEnum.Get)]
public virtual User GetObj([CacheKey]int Id)
{
using (DbDataReader dr = DbHelper.ExecuteReader(CommandType.Text, "s elect * from Users where id=" + Id))
{
if (dr.Read())
{
User u = new User();
https://www.doczj.com/doc/0214099573.html,erName = dr.GetString(dr.GetOrdinal(“uname”));
return u;
}
return null;
}
}
两个自定义的CustomerAttribute和一个枚举类型:
[AttributeUsage(AttributeTargets.Method,AllowMultiple = false)] public class CacheAttribute:Attribute
{
public string KeyName;
public OperateEnum Operate;
public CacheAttribute(string _key, OperateEnum _enum)
{
KeyName = _key;
OperateEnum = _enum;
}
}
public enum OperateEnum
{
Set,
Get
}
[AttributeUsage(AttributeTargets.Parameter,AllowMultiple = false)] public class CacheKeyAttribute: Attribute{}
说明如下:
1、getobj方法是虚的,因为只有虚方法拦截器才能拦住。
2、CacheAttribute第一个参数是你自己定义的Cache的key,这里我默认写成和
类名一样(其实CodeSimth生成DAL时也可一起生成),其实真正的Cache的key还要加上getobj方法的参数id(这一步会在拦截器中做掉),如:
Cache.add(“User”+id,user);
3、CacheAttribute第二个参数是个枚举,说明我现在这个方法是select,还是
update和Delete,因为拦截器不仅要拦截查询方法,还要拦截修改和删除方法,以便在你执行DelObj(id)、UpdateObj(id)时同步删除Cache中的对象。
4、CacheKeyAttribute这个类啥都没,它只能标记在参数上,它要告诉拦截器我
标记的这个参数就是Cache的key中的id。
好了,下面主角登场了拦截器类:
说明如下:
1、拦截器类要实现IInterceptor接口,并实现Intercept方法。
2、15行得到所有标记了CacheAttribute的方法。
3、21行的循环得到了标记了CacheKey的参数序号。
4、39行得到了真正Cache的key。
5、42-59行就是那部分被剥离出来的与缓存相关事情,现在交给了拦截器去做,
同时第25行才是去从数据库中取,54行如果返回值也就是从数据库中取到的对象不会空,56行则添加入缓存中。
6、61-65行如果您调用的是update和delete方法,则从缓存中删除,65行并
且执行真正的Delete或update方法。
调用时,修改一下DAL对象的实例化方式:
原来我们是直接New一个:
private static void GetProvider()
{
_instance = new UserDAL();
}
现在:
private static void GetProvider()
{
ProxyGenerator generator = new ProxyGenerator();
_instance = generator.CreateClassProxy
DALInterceprot());
}
ProxyGenerator是一个Castle提供的一个代理工厂类,用它Create一个DAL 实例。
总结:
1、如果现在就按以上方法写了DAL,如果想在开发时直接访问数据库,而不要
受缓存的干扰,那么就把您方法前的virtual关键字去掉,去掉后拦截器就不会拦截。
2、拦截器类和那两个自定义的Attribute类在任何项目可以重用。
3、在DAL类方法上标记的自定义标签可以用CodeSimth自动生成,即使自己写
一个CodeSimth模板中没有的方法也再简单不过了,比如:
[Cache("City", OperateEnum.Get)]
Public virtual Dictionary
4、有了AOP就可以不要在再每个页面的按钮事件中写步骤了,全部放到BLL层
用拦截器去拦截,只需要在放上标记一些Attribute。
5、如果以后我们想用xml缓存,或者用Memcached缓存,那只需要修改拦截器
即可。