在上一篇多样化实现Windows Phone 7本地数据库访问<1>采用两种方式Effproze和SQlite来验证Window Phone 7访问本地数据库.验证结论是可行的. 得到圆友及时反馈当然也发现一些细节上问题. 例如Effproze利用文件/内存模式 找不到指定的硬盘上数据库文件. Sqlite中支持Windows Phone 7 API没有相关文档. 以及SQlite创建后类似Effproze找不到指定存储文件等?类似这些问题 正在寻找和验证相关解决方案 稍后我会专门整理出一篇文章来详细说明. 本篇将继续验证Windows Phone 7访问本地数据库其他方式-Windows Phone DB
<1>初识Windows Phone DB
其实看到Windows Phone DB[以下简称WPDB]很偶然机会.当时我正在研究另外一家英国移动软件开发集成商自己做的开源数据库方式实现对WP7本地数据库访问.[详细见Windows Phone 7 gets open source database]时碰见WPDB的. 一个群组UK-Grouper 的DVP向我推荐Windows Phone DB.
WPDB是利用Silverlight的独立存储[IsolatedStorage]机制为WP7访问数据库加以支持. 目前的版本只是针对开发人员. 使用简单. 开源. 其实它内部存储数据的实质就是利用IsolatedStorage. Silverlight的IsolatedStorage是一种类似Cookie的静态存储机制.可以将一些基本类型(String,Int)的信息甚至是自定义类型序列化后的静态存储于客户端文件中.
独立存储[IsolatedStorage]是一个局部信任机制. 什么叫局部? 当你创建一个Silverlight应用程序时会在硬盘上创建相应独立的存储区域. 这里面独立是相对于不同Silverlight Project而言的. 当然如果应用程序中存在多个程序集[Project],那么存储空间在这多个程序集之间是共享的.
Silverlight限制了客户端Silverlight应用程序不能访问全部的文件系统,只能通过独立存储机制提供虚拟文件系统,访问数据流对象. 这样一来类似我们Application 有了自己一块硬盘空间一样.独立存储空间内就可以放置任意类型的文件. XML /.txt等. 4版本中空间大小也是可以控制的. 更多资源请参考.Introduce IsolatedStorage MSDN 由此也能看出Silverlight也涵盖WP7日常开发 当然还有Silverlight能做还不止这些 如下图:Silverlight 涵盖图
<2>Windows Phone DB给我们带来什么?
先不着急回答这个问题.WPDB是开源的 你在可以在CodePlex上下载它相关源码:
DownLoad on the CodePlex: Windows Phone DB[源码下载]
下载完源码用VS工具打开.预览整个Solutions:
Solutions中包含两个项目: 第一个为WPDB的源码项目 下面是对WPDB测试项目. 二者关系是测试项目对源码项目采取了引用. 先不管那么多运行起来看看效果:
页面只有一个Run tests按钮. 点击后运行提示Test Completed 测试完成 我们来看Button按钮下事件下代码的调用:[注释是自己添加的]
- private void RunTests_Click(object sender, RoutedEventArgs e)
- {
- //获取测试项
- foreach (var item in ResultPanel.Children)
- {
- if (item is TextBlock)
- {
- ((TextBlock)item).Foreground = new SolidColorBrush(Colors.White);
- }
- }
- CreateDBTest();//创建DataBase
- CreateTableTest();//Create Table
- SaveTest();//Save Config
- SaveSingleTableTest();//保存单表
- OpenTest();//打开数据库
- AddRangeTest();//添加一个范围数据[20条]
- RemoveRangeTest();
- RemoveRangeConditionTest();
- SaveFailsTest();//保存记录
- SaveWithEncryptionTest();//保存加密后数据-[看来还考虑加密]
- OpenWithEncryptionTest();//打开机密数据
- SelectConditionTest();
- LazyLoadingTest();//还有延迟加载-[很意外啊]
- AddRowToExistingTableTest();//添加一行数据库
- AddRowToExistingTableTestLazyLoad();
- DatabaseExists();//关闭数据库链接
- //测试完成提示
- MessageBox.Show("Test completed", "Silverlight Phone Database", MessageBoxButton.OK);
- }
由上面代码很明显能够看出, 方法包含操作也就是我们对数据库基本日常操作. WPDB完全创建一套自己的API[其实内部封装就是一个Silverlight 类库],这点和Effproze 在WP7访问方式完全不同. Effproze的API完全参考ADO.NET复制一个版本. SQlite则也是自己创建一套API.幸运的是这次我们能够看到WPDB的源码. 先不管大体方法中操作实现. 我们在回过头看看WPDB源码结构 分析如下:
如上分析可见.WPDB底层数据存储的实现 主要涉及到: DataBase/TAble的CRUD操作, 存储数据的加密和解密, 操作Exception异常自定义封装, IsolatedStorage数据存储以及文件流之间格式转换,Linq操作数据格式的支持,数据延迟加载等各个方面.从上源码分析来看. 这个WPDB实现总体来说还是比较简单的.不难理解.如果你觉得这些功能不能满足你的需求. 完全可以自己在如上代码添加更多的功能.
下面来看看对DataBase和Table表基本操作 我现在要创建一个PersionDB数据库 库中新建一个Persion表并添加 10条数据 如何实现: 创建DataBase:
- public static Database CreateDatabase(string databaseName, string password)
- {
- //如果存在抛出异常
- if (DoesDatabaseExists(databaseName))
- {
- throw new DatabaseExistsException(string.Format(DatabaseResources.DatabaseExistsExceptionText, databaseName));
- }
- //new一个DataBase新实例.
- //参数为:DataBaseName[数据库名称] password-[访问密码] false-[默认不采用延迟加载]
- return new Database(databaseName, password, false);
- }
创建先判断数据库是否存在, 然后new 一个Database实例看一下DataBase构造函数:
- private Database(string databaseName, string password, bool useLazyLoading)
- {
- //如下全部类DataBase封装属性
- _databaseName = databaseName;//数据库名称
- _password = password;//数据存储加密的密码-[注明:加密和解密都需要密码]
- _useLazyLoading = useLazyLoading;//是否启用延迟加载
- //封装了一个Collection 来存储当前DataBase下所有的Table表
- _tables = new ReadOnlyCollection<ITable>(new List<ITable>());
- _loadedTables = new Dictionary<Type, bool>();
- }
构造函数中封装DAtabase基本属性, 其中有必要说一下ReadOnlyCollection<ITable> 它其实目的是在Database对象创建一个Collection集合来存储表结构. 里面表结构实现是父类接口ITable.有了DataBase我们创建一个Persion表:
- //创建库 [调用代码]
- Database.DeleteDatabase("test");
- Database db = Database.CreateDatabase("test");
- //创建相应表
- db.CreateTable<Person>();
创建表CreateTable方法定义:
- public void CreateTable<T>()
- {
- //判断表是否存在
- if (DoesTableExists(typeof(T)))
- {
- throw new DatabaseExistsException(string.Format(DatabaseResources.TableExistsExceptionText, typeof(T).FullName));
- }
- else
- {
- //奥 尽然这种写法 已经利用定义Collection定义好List大小Size 难道也直接考虑到List性能
- List<ITable> tables = new List<ITable>(_tables);
- //创建表 其实就是New 一个Table实例 指定Table的名称和访问密码
- tables.Add(SilverlightPhoneDatabase.Table<T>.CreateTable(_databaseName, _password));
- //创建成功后 把这个新建的表添加指定的数据库中
- _tables = new ReadOnlyCollection<ITable>(tables);
- }
- }
创建表同时也与表实例类型关联. 这是我们需要定义一个Persion实体类[源码附有下载]. 把新建的TAble表添加到Database表存储集合中, 实现与数据库的关联. 有了表和数据库 快速插入10条记录: 数据插入操作:
- private void SaveTest()
- {
- //创建库和表
- Database.DeleteDatabase("test");
- Database db = Database.CreateDatabase("test");
- db.CreateTable<Person>();
- //数据库表存在情况下
- if (db.Table<Person>() != null)
- {
- for (int i = 0; i < 10; i++)
- {
- //添加数据 NewRandomPerson返回一个随机的实体类Persion
- db.Table<Person>().Add(NewRandomPerson());
- }
- //保存数据
- db.Save();
- this.SaveDBLabel.Foreground = new SolidColorBrush(Colors.Green);
- }
- else
- {
- this.SaveDBLabel.Foreground = new SolidColorBrush(Colors.Red);
- }
- }
数据添加到DAtaBase对象下属性ReadOnlyCollection<ITable> Tables集合中,db.Save数据保存在源码重写成两个方法: 下面保存所有的数据库和所有表到独立存储空间文件上:
- public void Save()
- {
- try
- {
- //创建应用程序类型独立存储
- using (IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication())
- {
- //删除存储文件 一个数据库对应一个存储文件
- if (store.FileExists(_databaseName))
- {
- store.DeleteFile(_databaseName);
- }
- //创建存储文件
- using (IsolatedStorageFileStream stream = new IsolatedStorageFileStream(_databaseName, FileMode.OpenOrCreate, store))
- {
- //把数据库内容Database 写成文件流方式保存
- WriteDatabaseToStream(stream);
- stream.Close();
- }
- }
- foreach (var item in Tables)
- {
- //延迟加载方式
- if (_useLazyLoading)
- {
- if (_loadedTables[item.RowType])
- { item.Save(); }
- }
- else
- { item.Save(); }
- }
- }
- catch (Exception ex)
- {
- throw new SaveException(ex);
- }
- }
看到了吧这就是数据真正存储到独立存储空间上文件里方法. 注意独立存储Isolated Storage根据应用程序作用域不同分为应用程序和站点两种类型. 使用时分别用不同对象创建,当前采用应用程序方式.
存储时利用数据库名称作为文件名, 对应关系为 一个数据库对一个独立存储文件. 数据库库保存细节是把数据库内容及Collection写成Stream字节流方式存储到文件中 WriteDatabaseToStream编码如下:
- public void WriteDatabaseToStream(Stream stream)
- {
- string serilizedInfo = string.Empty;
- serilizedInfo = _databaseName;
- //获取数据库中表数据
- foreach (var item in _tables)
- {
- serilizedInfo = string.Concat(
- serilizedInfo,
- Environment.NewLine,
- CreateFormattedTableType(item.RowType));
- }
- if (!string.IsNullOrEmpty(_password))
- {
- //如果有采用了加密方式 则把数据进行加密 返回加密的字符串 .在存储到文件中
- serilizedInfo = Cryptography.Encrypt(serilizedInfo, _password);
- }
- using (StreamWriter writer = new StreamWriter(stream))
- {
- //把数据字符串写入字节流中 方式写到独立存储空间硬盘文件上
- writer.Write(serilizedInfo);
- writer.Flush();
- writer.Close();
- }
- }
获取字节流格式后, 获取数据库对应每个表, 利用string.Concat方法拼接字符串, 如果在创建表时设置需要加密则功过加密方法返回加密后字符串, 最后把字符串写入存储流中 进行保存独立存储硬盘空间上 实现了数据的存储.
如上实现了Windows Phone DB 从创建数据库-创建数据表结构-插入数据-保存数据到独立存储空间上,整个流程. 当然更多操作请下载源码参考.
到了这儿我在回到这个小节的主题,Windows Phone DB 给我们带来了什么?
Windows Phone DB给我们带来利用独立存储方式现在WP7对本地数据访问支持最完整解决方案.它把Silverlight的独立存储机制运用在数据库存储上最大化了. 它利用Silverlight类库模拟了一个小型的数据库存储系统[虽然很多东西不支持]. 你可以看出数据库和表结构 完全可T-Sql没有任何关联, 利用类于类之间关系进行约束的.
很多人又不禁要问. 这样的形式是不完整的. 我要它支持View. 存储过程Proc. Transaction事务操作等. 那么剩下工作就是采用类于类之间关联进行约束创建, 它开辟了在WP7利用独立存储方式模拟数据库存储功能一种独特视角[虽然不是最好方式] 开阔我们解决问题更广的视野. 这一点是我个人为Windows Phone DB 做的最成功的地方. 但从这点于Effproz和SQlite来说 WPDB是具有创造性的思维的.
当然如果你认为它不能满足你的工作, 太过简单, 对于一个难度不大 但视角独特 而且开源项目来说, 你完全可以在这个基础之上加上更多的功能 模拟出更好数据库支持. 如果你好的建议 或疑问请在留言中提出.
<3>Windows Phone DB小节
当然短短一篇文章也许无法更加详细阐述WPDB所具有的各种特点. 我也是作为一个初学者利用短短一种时间对源码进行摸索. 虽然WPDB性能和实用性不及Effproz和SQlite 但作者的创造性思维的方式 给我的映像深刻.
由此WPDB和T-SQl没有任何关联. 所以就没有QueryTool查询工具可言了.另外对于Silverlight异步通信而言, 本次源码中并没有实现对类实例化进行远程传输JSon格式的实现, 其实这个功能完全可以再WPDB基础之上模拟出来.
至于说性能和其他功能完善. WPDB和其他数据库没有任何可比性. 那是因为没有共同的基础条件. 但是WPDB在创造性可以说独树一帜的. 以上均为我个人详细分析源码后获得一点感受.如果你有更好意见和建议可以再留言中提出.