背景是有一个游戏服务器一直以来都是写SQL的, 后来改过一段时间的redis, 用的是别的员工写的类orm方式将实体类型映射成各种key-value对进行写入, 但是仍有一个缺点就是需要在增\删\改的时候显式调用API, 更糟糕的是要注明删\改的字段名, 不然就会整个实体重写入. 实际使用中经常会出现写错字段名, 或是重命名字段之后忘记修改旧的字段名字符串参数, 甚至忘记调用update API导致数据没有保存...(同样的情况也发生在写SQL的时候)

其实增/删/改用到的SQL或是reids api都非常简单, 都可以自动生成, 免去反复书写的麻烦也容易写错的问题.

于是乎, 我计划通过监视/拦截增/删/改 方法以在数据修改的同时自动生成SQL/Redis调用, 来对改属性值进行保存. 其实增删比较简单, 只要自定义一个集合类型继承下IEnumerable<T>接口, 然后实现增删方法即可, 对原代码的修改量也是极小的.

但是改就有点麻烦了, 需要拦截属性的set方法, 这是以前没有怎么用过的AOP领域.

一些调查之后, 觉得有两种方式比较适合.

一是静态织入, 就是用类似代码模板的方式, 先写实体类型模板, 然后生成真正的实体类型类库, 在生成的时候进行修改在属性的set方法中注入PropertyChangedNotify事件, 服务中使用的是生成后的实体类库. PostSharp貌似不错, 但是不想引入更多的开销就罢了. 用T4或者上Emit或许不错.

二是动态, 大致上就是生成一个代理类型, 拥有原类型的所有接口, 但是重新给实现了一遍. 可以是组合的方式, 将原类型实例作为私有成员保存, Emit生成所有原类型的开放方法; 也可以是继承的方式, 重载. 考虑到对原代码的修改量, 我觉得继承重载不错.

比如原实体类型如下:

public class Person
{
public string Id{get;set;}
public string Name{get;set;}
}

对这个类实现修改保存的托管, 那么直接使用这个类型是不行的了(除非方案1静态修改注入), 只有使用动态生成的代理实体类, 而如果使用组合方式的代理类型, 在OO的情况下很别扭, 所以我选择用继承重载的方式. 这就需要对这个实体类型进行改造, 将所有需要监视的属性修改成virutal声明 ctrl+H搞定.

然后, 因为是增删改, 所以Key是必不可少的, 那么实体需要用attribute标记出Key属性, 支持多属性组合Key. 因为原代码用过一段时间的entityframework, 所以这个改造非常顺利.

最后的类型定义如下:

[DataContract]
public class Person
{
[Key]
public string Id{get;set;}
public virtual string Name{get;set;}
}

首先用attribute标记Person为需要代理的类型, 便于在实际使用中纠错和预加载, 然后标记Id属性为Key, 并且不需要重载, 其他属性声明为virtual表示需要重载进行监视.

然后手写一个代理类型:

public class PersonProxy : Person
{
public override string Name
{
set
{
base.Name= value;
Interceptor.Invoke(this, "Name", value);
}
}
} public static class Interceptor
{
public static void Invoke(object instance, string name, object value)
{
//log
}
}

再定义个Factory的静态类, 实现静态泛型函数Create<T>()来创建Person的代理类型PersonProxy. 其实后面调用的都是代理类型, 只是使用方法和Person一致, 兼容旧代码.

只是这样的话, 还不如使用静态织入了. 所以上面手写的这个PersonProxy需要动态生成, 这样通过泛型来支持所有自定义class, 减少频繁修改. 于是Emit登场了.

不过我对IL和Emit认识浅薄, 我的办法是使用VS自带工具 -- ildasm, 用它打开刚才生成的PersonProxy查看IL代码, 反复对比总结之后终于将这个PersonProxy类型的动态生成用Emit实现成功并修改成泛型通用. 这套特殊的IL学习方法论真是屡试不爽. 方法大致如此抛砖引玉, 所以这里也就不贴emit的代码了. Invoke织入也可以做各种进化, 以达成更高级的功能, 比如invoke在赋值前, 写入失败回退throw exception等等...

这样ProxyFactory.Create<T>()函数就写好了.

在Invoke里生成需要SQL或是redis set方法就可以了.

最后再实现增删代理的集合类型, 在Add的时候直接将插入的对象通过ProxyFactory.Create<T>()转成proxy子类对象, 为了避免Add之后继续使用传入的原对象而丢失对属性set的监控, 修改Add的声明为Add(ref T obj)强行修改引用为代理对象. 从入口处根绝, 完美.

理论Ok, 于是封装成类库.

最后放上代码地址 : https://github.com/Roytin/SmartDb

用AOP思想改造一个服务器的数据存储的更多相关文章

  1. 【Java编程思想阅读笔记】Java数据存储位置

    Java数据存储位置 P46页有感 一.前置知识 栈是由系统自动分配的,Java程序员对栈没有直接的操作权限, 堆是所有线程共享的内存区域,栈 是每个线程独享的. 堆是由程序员自己申请的,在使用new ...

  2. Html5——WEB(客户端)数据存储

    在客户端存储数据 HTML5 提供了两种在客户端存储数据的新方法: localStorage - 没有时间限制的数据存储 sessionStorage - 针对一个 session 的数据存储 之前, ...

  3. node.js基础:数据存储

    无服务器的数据存储 内存存储 var http = require('http'); var count = 0; //服务器访问次数存储在内存中 http.createServer(function ...

  4. HTML5客户端数据存储

    HTML5 使在不影响网站性能的情况下存储大量数据成为可能.之前,这些都是由 cookie 完成的,cookie不适合大量数据的存储,因为会影响速度. 举个例子: var obj = {x:1}; / ...

  5. Android简易数据存储之SharedPreferences

    Andorid提供了多种数据存储的方式,例如前面说到的“Android数据存储之SQLite的操作”是用于较复杂的数据存储.然而,如果有些简单的数据存储如果采用SQLite的方式的话会显得比较笨重.例 ...

  6. wp8.1 Study10:APP数据存储

    一.理论 1.App的各种数据在WP哪里的? 下图很好介绍了这个问题.有InstalltionFolder, knownFolder, SD Card... 2.一个App的数据存储概览 主要分两大部 ...

  7. html5 之本地数据存储

    HTML5 提供了两种在客户端存储数据的新方法: localStorage - 没有时间限制的数据存储 sessionStorage - 针对一个 session 的数据存储 cookie与webSt ...

  8. python轻量级数据存储

    python为开发者提供了一个轻量级的数据存储方式shelve,对于一些轻量数据,使用shelve是个比较不错的方式.对于shelve,可以看成是一个字典,它将数据以文件的形式存在本地.下面介绍具体用 ...

  9. iOS开发-数据存储NSCoder

    软件中永远绕不开的一个问题就是数据存储的问题,PC的时候一般都是选择在数据库中存储,iOS如果是和后端配合的话,那么不需要考虑数据存储的这个问题,上次写了一下plist的存储,不过数据都是存储一些简单 ...

随机推荐

  1. php $_SERVER中的一些选项说明

    1, $_SERVER['SCRIPT_FILENAME'] 和 常量 __FILE__的区别(一般情况下两者的显示相同,都是显示文件的绝对路径,包括文件名,显示的起点是 电脑根目录 /) ①,如果在 ...

  2. rm与管道使用

    一 问题初始:用通常意义的管道使用这样可以:(1)ls -l | sed -n '/~$/p' 我用显示出系统自己建立的备份文件这时,我想删除这些文件,我仍然使用了管道,并执行了以下命令(2)ls - ...

  3. MongoDB常用命令总结

    查看数据库 show dbs; 选择某个库 use db; 查看库下的表(暂且说成是表,mongodb中称表问文档) show collections; 插入数据 db.table.insert( { ...

  4. L1范式和L2范式

    正则化(Regularization) 机器学习中几乎都可以看到损失函数后面会添加一个额外项,常用的额外项一般有两种,一般英文称作ℓ1ℓ1-norm和ℓ2ℓ2-norm,中文称作L1正则化和L2正则化 ...

  5. 【王者荣耀之IT大神版】1.1版本升级之“投降机制”

    版本:1.1 关于“投降机制”的理论基础与灵感来源于<微习惯>这本书. 简单来说,微习惯就是很小很小的习惯,比如说,每天做一个俯卧撑,每天看一页书等等.我们以前也许有过很多的计划,但却总是 ...

  6. vue+大文件分片上传

    最近公司在使用vue做工程项目,实现大文件分片上传. 网上找了一天,发现网上很多代码都存在很多问题,最后终于找到了一个符合要求的项目. 工程如下: 对项目的大文件上传功能做出分析,怎么实现大文件分片上 ...

  7. jpa命名规范

      Keyword Sample JPQL snippet And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname ...

  8. 去除html页面中GET《 http://localhost:8080/favicon.ico 404 (Not Found)》

    html5页面中经常会遇见以下问题,总是看着碍眼,那么怎么解决呢? GET http://localhost:8080/favicon.ico 404 (Not Found) 解决的方法: <l ...

  9. XA: 事务和两阶段提交

    本文原文连接:http://blog.csdn.net/bluishglc/article/details/7612811 ,转载请注明出处! 1.XA XA是由X/Open组织提出的两阶段提交协议, ...

  10. hdu 5088 高斯消元n堆石子取k堆石子使剩余异或值为0

    http://acm.hdu.edu.cn/showproblem.php?pid=5088 求能否去掉几堆石子使得nim游戏胜利 我们可以把题目转化成求n堆石子中的k堆石子数异或为0的情况数.使用x ...