上一篇多样化实现Windows Phone 7本地数据访问<4>——Rapid Repository 中初步的介绍Repid Repository作为Windows phone 7数据库存储原理Repid具有特点以及数据CRUD基本操作.Rapid Repository 是一个基于WP7开源的数据库. 上周联系Rapid 数据库的作者Sean McAlinden.有人也针对下一个版本的做出相关询问.Sean McAlinden明确提出下个版本中将会加入对视图/索引模型的支持,提高查询的性能,以及对事务的支持.也就是在保持数据性能和完整性上两个方向上继续完善Rapid.
本篇将对于Rapid数据存储后台处理机制一些具体细节进行逐步说明. 这样做目的是把Rapid数据处理细节流程完整的暴露出来. 以及Rapid采用Linq查询,数据的延迟加载,数据的缓存处理.等.进一步说明.
<1>Rapid Repository数据处理机制
Rapid Repositroy是开源的,你可以在http://rapidrepository.codeplex.com/ 上下载到Rapid的源码以及作者写的相关数据处理相关Demo.
对于移动平台的数据库都能找到相关开源的替代解决方案.对于目前WP7因基于Silverlight平台构建特点..对于数据本地存储选择一直都没有得到官方的支持,但开源社区的力量总是能为我们开辟一些基于WP7目前架构特点独特数据存储视角. Rapid也就在本月份刚发布1.0正式版.相对于Sqlite和Effect DB4O Rapid算是后来者. WP7上本地DB选择会随着WP7开发需求增加会逐渐变多, 而对于想深入理解WP7数据存储处理机制,Rapid开源的特点无不给了我们"窥视”WP7不被暴露的数据处理机制的极好机会.
下载源码成后进行解压 打开Rapid解决方案 预览源码结构:
我们大概就能从作者对源码归类初步能了解Rapid具有功能.对于类之间关联关系做了简单归纳, VS2010中的Class DiaGram看的不太清楚.做了一幅图:
如上看到是存在类与类存在关联关系的一部分.[其他类相对比较独立]
IRapidEntity则是对所有存储实体类Entity定义一个唯一标识的属性Guid.
ISerialiser则是对应对实体的序列化和反序列操作[和Serialiser类相对应]
IFileManager实体序列化完成后产生一些序列化后文件存在在WP7独立存储空间 FileManager则是对这些序列化后实体文件管理类.
现在从添加一条数据记录方式来跟踪一下Rapid如何把数据存储DB中整个过程,我们先定义一个Customer类:
1: public class Customer : IRapidEntity
2: {
3: public Guid Id { get; set; }
4: public string FirstName { get; set; }
5: public string LastName { get; set; }
6: public Address Address { get; set; }
7: public List<ContactNumber> ContactNumbers { get; set; }
9: public Customer()
10: {
11: this.ContactNumbers = new List<ContactNumber>();
12: }
13: }
Customer类继承IRapidEntity接口目的实现对实体类的唯一标识属性Guid. 定义CustomerRepossitory数据库操作类:
1: public class CustomerRepository : RapidRepository<Customer>
2: {
3: /// <summary>
4: /// Add new Customer Entity to Repid DB
5: /// Sign by chenkai Data:2010年11月14日11:12:35
6: /// </summary>
7: /// <param name="entity">Customer Entity</param>
8: /// <returns>Add Customer</returns>
9: public override Customer Add(Customer entity)
10: {
11: var customer = base.Add(entity);
12: RapidContext.CurrentContext.SaveChanges();
13: return customer;
14: }
15: }
CustomerRepository类继承RapidRepository所有数据库相关操作,当我们添加WP7一个Customer实体时:
当我们点击Save按钮时Repid按钮将把Customer实体保存Rapid本地数据库中, 跟踪源码发现Save方法下操作调用CustomerRepository 的父类RapidRepository的Add()方法, 具体实现:
1: public virtual TEntity Add(TEntity entity)
2: {
3: Contract.Requires("entity", entity != null);
5: RapidContext.CurrentContext
6: .AddOperationRequest(new OperationRequest(entity, typeof(TEntity), OperationRequestType.Add));
7: return entity;
8: }
其中Contract.Requires()方法调用初始实体对象对非空异常的进行独立封装. RapidContext则是获取Repid数据库操作类的上下文信息. CurrentContext获取当前保存操作上下文对象.看一下定义:
1: public static RapidContext CurrentContext
2: {
3: get
4: {
5: if (context == null)
6: {
7: lock (syncRoot)
8: {
9: if (context == null)
10: context = new RapidContext();
11: }
12: }
13: return context;
14: }
15: }
显然加入一个Lock锁机制保证每次保存Entity操作对象在整个上下文中唯一.
AddoperationRequest()则把要保存的实体类封装成对后台Rapid数据库一次操作请求OperationRequest.添加到RepidContex属性List<OperationRequest> operationRequests集合中:
1: /// <summary>
2: /// Adds the operation request.
3: /// </summary>
4: /// <param name="operationRequest">The operation request.</param>
5: public void AddOperationRequest(OperationRequest operationRequest)
6: {
7: Contract.Requires("operationRequest", operationRequest != null);
8: this.operationRequests.Add(operationRequest);
9: }
我们看一下一个操作请求OperationRequest封装:
1: /// <summary>
2: /// OperatorRequest Static Struct
3: /// Sign by chenkai Data:2010年11月15日11:59:07
4: /// </summary>
5: public OperationRequest(object entity, Type entityType, OperationRequestType operationRequestType)
6: {
7: Contract.Requires("entity", entity != null);
8: Contract.Requires("entityType", entityType != null);
10: if (operationRequestType != Context.OperationRequestType.Add)
11: {
12: this.EntityId = ((IRapidEntity)entity).Id;
13: }
15: this.Entity = entity;
16: this.EntityType = entityType;
17: this.OperationRequestType = operationRequestType;
18: }
当我们保存一个实体类时,OperatorRequest类作为对后台数据库一次操作请求,则进一步对添加Entity进行封装.保存实体信息同时指定实体类型.EntityID则是实体的唯一标识GuID.这时返回封装操作请求全部添加在List<OperationRequest> operationRequests集合中, 这个请求会一直呆在OperatroContext上下文直到调用了SaveChanges()才回给实体创建一个Guid唯一标识并保村Rapid数据库,如何来保存?SaveChange()方法定义:
1: /// <summary>
2: /// Saves the changes.
3: /// </summary>
4: public void SaveChanges()
5: {
6: foreach (OperationRequest request in this.operationRequests)
7: {
8: switch (request.OperationRequestType)
9: {
10: case OperationRequestType.Add:
11: ((IRapidEntity)request.Entity).Id = Guid.NewGuid();
12: var addString = this.serialiser.Serialise(request.EntityType, request.Entity);
13: this.fileManager.Save(request.EntityType, ((IRapidEntity)request.Entity).Id, addString);
14: AddToCache(request.EntityType, (IRapidEntity)request.Entity);
15: break;
16: }
17: }
18: }
当添加完实体只有在调用SaveChange()方法数据的更改才回体现到Rapid数据库中. 同样如果要是所有对实体Entity更改得到持久化文件方式保存,SaveChange()则是必须要核心调用的.
Savechange()中当一个实体进入经过OperatorRequest操作请求的封装.则可以通过OperationRequestType枚举在判断当前对实体操作类型.[添加]. 判断成功后这时才回对实体生成一个唯一标识的GUID. 然后通过Serialise()方法对实体Customer进行序列化成JSon格式字符串返回.接着FileManager通过Save()方法把实体序列化后JSon字符串以文件方式存储在WP7的独立存储空间上. 进阶着把操作的实体类信息在缓存中进行更新.保证每次数据库操作能在第一时间和缓存中数据进行同步.
自此Rapid保存数据方式具体流程 就全盘而出. 制图如下:
如上就是Rapid数据处理数据整个流程的细节.整体来看并不复杂, Rapid 定位是WP7的文档数据即NoSQl.数据持久化存储方式同样还是用独立存储中建立Json格式的文件. 但是Rapid的处理数据方式上相对于WindowsPhone DB和Effiect有些不同.这个稍后我会说到.
<2>Rapid Repository缓存处理
在上一篇中多样化实现Windows Phone 7本地数据访问<4>——Rapid Repository曾提出关于Rapid数据性能上质疑,当时我并没有看到Rapid的源码.后来在Rapid的作者关于数据性能表现留言中,提到Rapid采用缓存机制来处理这方面需求. 比较感兴趣.作者Sean McAlinden也针对这个问题做了一个相关说明. 这里暂且不提 先回到源码中来 saveChange()方法中缓存操作来.
1: case OperationRequestType.Add:
2: AddToCache(request.EntityType, (IRapidEntity)request.Entity);
3: break;
4: case OperationRequestType.Update:
5: EntityCache.Update(request.EntityType, request.EntityId, request.Entity);
6: break;
7: case OperationRequestType.Delete:
8: EntityCache.Remove(request.EntityType, request.EntityId);
9: break;
如上可以看出,Rapid中默认情况下在数据增加/修改/删除中添加或更新缓存保持数据同步,类似做相关数据查询所有数据全部缓存中已经同步,查询数据源也就从序列化后Json文件转到内存中来,这样一来性能会大大提升.
类似我们现在又一个场景,在WP7中去搜索一个Customer实体:
当点击Search按钮搜索.Rapid支持对象之间Linq操作.类似在搜索中可以指定搜索的选项指定过滤Customer多个字段:
1: /// <summary>
2: /// Search Customer by Key Word
4: /// </summary>
5: private static List<Customer> GetCustomers(string searchText)
6: {
7: CustomerRepository repository = new CustomerRepository();
8:
9: //get all()方式 没有用到Cache.
10: return repository.GetAll().Where(x =>
11: x.FirstName.ToLower().Contains(searchText.ToLower()) ||
12: x.LastName.ToLower().Contains(searchText.ToLower()) ||
13: x.Address.PostCode.ToLower().Contains(searchText.ToLower())).ToList();
14: }
如上把搜索框中内容搜索覆盖到Customer实体的FirstName/LastName/Address三个字段属性中查找.注意这里采用数据是GetAll()方式通过Where关键条件进行过滤,并没有采用缓存方式.
类似我们现在要提高用户体验要求.在加载搜索页时自动加载数据到内存中, 无需通过GetALL()去查找文件 进行IO的读写.而Rapid正好支持这种数据预加载方式放到缓存中存储. 加载过程作为后台线程单独处理,无须主线程等待缓存加载完成才做出响应. 而针对不同实体这种Rapid预加载方式只能运行一次.当时我很不理解,后来作者回复中, 类似在SearchCustomer.Xaml的initialise 方法中 当页面第一次加载时才被调用,作者的解释这样处理对于实体预加载只做一次的处理 是为了日常数据中合理管理操作缓存. 预加载方式:
1: //预加载
2: 1.EntityCache.EagerLoad<Customer>();
针对这个问题,作者也提到已经打算在下一个版本加以完善. 另外有人也提出一个问题:
“当我们编写Windows Phone 7 Appliction时 自己机器配置内存有限 Rapid缓存方式必然会占用一部分内存, 如果关闭Rapid缓存释放更多内存资源”.
作者也针对这个问题做了很好处理: 可以在我们Wp7 appliction 初始化方法及App.xaml中InitializePhoneApplication()方法中.直接过滤掉指定实体的缓存设置. 其实这里你应该能猜到后台处理缓存方式利用实体的类型即Entity.Type方式进行标识的:
1: public MyApplicationStartup()
2: {
3: //取消缓存设置
4: EntityCache.NoCache<Customer>();
5: }
每次加载类型是在从缓存中排除的实体类型.
篇幅有限 这里不在赘述Rapid对缓存处理原理介绍.从上面介绍预加载方式应该能看出端倪.如果想连接更多请查看Rapid源码.
<3>Rapid Repository小结
如上基本介绍Rapid数据库在Windows Phone 7上数据处理原理和缓存机制. 说道Rapid采用缓存与Json格式文件存储进行同步.这种方式相对Sqlite,Efficet WindowsPhone DB都有些不同. 总结关系图如下:
EffiectDB方式内存和文件模式在数据进行访问只能选择一种方式进行.
SQlite方式则更加单一只能以一个硬盘上文件持久化数据.每次文件读写都需要固定的IO读取操作.
DB4O和Windows Phone DB 则采用WP7的独立存储机制写入文件流方式存储.
Rapid方式内存和文件模式同时可以共存,数据来源可以有用户决定. 内存模式可以禁用.这样一来相对于Efficet DB就显得非常灵活.把真正使用权交给用户来决定.
如果说Windows Phone DB在Wp7上采用独立存储方式模拟一个shcme,表,字段属性关系型数据库.是具有开创性的那么Rapid则灵活使用数据存储并对以用户对数据要求为核心积极数据反馈方式则是非常实用的. SQlite则一直传统的File模式,DB4O则没有成熟尚需时日.
针对Windows Phone 7本地数据库特点 有时间会整理出一个总结篇,详细比较Sqlite,DB4O,Windows PhoneDB Rapid,Efficet等数据处理的优势.如上本篇关于Rapid在数据处理原理上一次详细解析.如有疑问请在留言中提出.