我一直想把EntityFramework(简称EF)的那一套搬过来,应用于HTML5 SQLite。 幸运的是,我几乎做到了,有些功能无法完成的那是因为SQLite本身不支持。至少从现在已经完成的功能来看,我们几乎可以像EF那样去操作数据库。
在我们这个框架中,所有对数据库的访问都封装在了命名空间"nova.data"下面。其下主要包括了以下类:
- nova.data.DbContext
- nova.data.Entity
- nova.data.Repository
- nova.data.Queryable
下面我将逐一详细阐述。
4.1 nova.data.DbContext
这个类相当于是数据库的定义。我们定义的数据库的类都应该继承自这个类。下面让我们先来看个例子吧。
这段代码定义了一个数据库,但实际上里面只有一张表,关于表的定义我会在下一节中详述。这里唯一的一张表就是"this.targets", 类型是"nova.data.Repository",相当于EF中的DbSet,实现的都是仓储模式。
请注意上面的第6行代码,nova.data.DbContext构造函数的第2个参数是版本号(字符串)。这个版本号的神奇之处在于,当它改变的时候,那么在初始化数据库的时候(通常在index.html),整个数据库就会重新创建。如果你希望数据库在重新创建的时候能够带一些初始数据,那么你可以覆写initData方法。
让我们把目光再次投向第10行代码。上面我说了,这一行代码是在这个数据库中定义了一张表。如果还想添加更多的表怎么办呢?对于聪明的你来讲一定不成问题。只需要在这个at.domain.AtDbContext下增加一些属性,并且这些属性的类型都是nova.data.Repository。值得一提的是,repository的第一个参数是一个构造函数,并且类型必须继承自nova.data.Entity。具体的关于repository个entity的定义,我会在后面的内容中详细阐述。这里你只需知道如何定义一个数据库,如何往数据库中添加/删除/更新表。
4.2 nova.data.Repository
这个类相当于EF中的DbSet,跟nova.data.DbContext一起实现了仓储模式。所谓仓储模式,就是说任何对repository的增加/删除/更新操作都只有在调用了DbContext.saveChanges之后才会生效。我们这个框架中提供的数据访问也是这样一种模式,如此你便可以同时提交多条数据更改了,而不是每一次更改都要调用一次数据库。
这个类相当于EF中的DbSet,主要用于定义表。其第一个参数是实体类的构造函数,实体类需继承自nova.data.Entity, 第2个参数是字符串,及数据库中表的名字。
这个类下面有很多方法,我就不一一赘述了。发个截图吧,相信从方法的定义大概看得出是做什么的:
这个类实际我一直想着改造下的,那就是让它继承自nova.data.Queryable. 不过现在这样用着也满足需求,也挺方便。
这个类有个已知的BUG,会在本章末尾阐述。
4.3 nova.data.Entity
我在设计这个类的时候,我也试着模仿EF中的CodeFirst模式的代码。就是说每一个字段的数据类型在定义这个实体类的代码中体现,幸运的是我几乎实现了这样的功能。只要你在定义实体类的时候,给每一个属性指定一个初始值,那么在创建数据库的时候,就会根据这个初始值来指定数据类型。目前支持的数据类型有:integer, decimal,string, date,boolean。如下面的代码:
在我们的框架下开发应用程序,所有的本地数据库实体类都应该继承nova.data.Entity。
这个类只有一个属性integer id,这也是SQLite数据库每一张表默认都会带的字段。因此,在你的数据库实体类的定义中,不应该再定义id这个字段了。除了这个id属性,这个基类还定义了一些方法,主要是用于处理数据类型的。
这个类的另一个强大之处在于,以Target实体类为例,当你同时往数据库插入了多条target记录,并调用了dbContext.saveChanges方法时,每一个target的id会得到更新。这个特性也是模仿EF的,因为我们在使用EF的过程中发现这个特性实在太有用了。相信你在使用我们框架的过程中也会喜欢这个特性的。
4.4 nova.data.Queryable
这个类是模仿Linq中的IQueryable的,其工作原理也是和IQueryable类似。请看下面这行代码:
db.targets.where(“price >= 10”).where(“price < 20”).orderBy(“category”).thenBy(“name”).toArray(callback).
如果你熟悉Linq,那么这行代码你一定不会陌生。是的,我们的框架就实现了这样的功能。上面这行代码,在调用toArray之前,每一个查询/排序条件都没有提交到数据库,而是放在了内存当中,只有当调用了toArray时,才会执行数据库的查询。
请注意上面这行代码的第一个where,这个where方法实际上是repository中定义的,但返回的却是queryable,而后面的where和orderBy等都是queryable的实例方法。这正是模仿Linq设计的巧妙之处。
这个类的其他方法都比较一目了然了,就贴个截图吧:
4.5 常用例子
前面我把我们框架中对于本地数据库访问的每一个成员都做了详细的介绍,但如果你对JS面相对象或者EF不那么熟悉的话,可能还是看得有些云里雾里。好吧,下面我就贴一些常用的例子,相信这些例子一定能让你领略到这个框架的独特魅力。
4.5.1 定义数据库
先上代码:
这段代码只定义了一个targets表,你当然可以增加更多的表,只需要把第3行代码复制几份,然后改改名字就可以了。
4.5.2 定义实体类
先上代码:
这里定义了一个Target类,还有很多个实例方法,看上去跟普通的类没有什么区别,唯一的区别就是继承自nova.data.Entity.
4.5.3 查询
4.5.4 插入
4.5.5 更新
4.5.6 删除
4.6 已知问题
目前有1个问题无法解决。
当同时执行批量插入、批量更新、批量删除的时候会报错。因此,如果你想同时执行这些批量操作的话,那么应该让一种批量操作完成之后再执行另一个批量操作。