SQLite与“继承映射”
SQLite
SQLite 的网站 (sqlite.org) 对SQL进行了如下描述:
SQLite 是一个可实现独立、无服务器、零配置、事务性 SQL 数据库引擎的软件库。
SQLite 完全驻留在客户端进程中,这使之成为一种嵌入式数据库。 在使用期间,SQLite 数据库的运行空间是一个存储在客户端文件系统的某一位置中的单个文件,并且安装空间通常也相当小。
尽管如此,SQLite 数据库的功能却是极其丰富的,因为它支持大部分SQL-92 规范,只是去除了 RIGHT 和 FULL OUTER JOIN、ALTER TABLE、某一触发器支持、GRANT/REVOKE 以及写入 VIEW 等几项内容(更详细的说明请参见SQLite 网站)。令人印象深刻的是支持的功能数量,包括事务和各种数据类型。这使 SQLite 十分适合只需轻型 SQL的情形。
有关更详细的 SQLite 体系结构和用法说明,还可以查看这里。
继承映射
关系数据库中的表之间不存在继承关系,为了将面向对象中的继承关系映射到关系数据库中,可以使用以下三种继承映射策略:
每个继承层次一张表
描述一个继承关系只用一张表,也就是说子类所使用的表与父类相同。
优点:
- 维护方便,在任何情况下都只需要处理一张表。
- 执行效率最高(无需外连接)。
缺点:
- 需要在数据库中加入额外的区分子类的字段。
- 不允许对子类成员属性对应的字段定义
Not Null
约束。 - 灵活性差,表中冗余字段会随着子类的增加而越来越多。
每个具体类一张表
每个具体类对应一张表,有多少具体类就需要建立多少个独立的表。每张表中包含了具体类的所有属性,包括从基类继承过来的属性。
优点:
- 映射的灵活性大,可以对每个属性进行单独的配置(包括继承的属性)。
- 对于子类的查询只需要一张表。
缺点:
- 存在太多冗余字段。
- 如果需要对基类进行修改,则需要对基类及其所有子类都进行修改。
每个子类一张表
每个子类使用一张表,但这些子类所对应的表都关联到基类所对应的表中。也就是子类对应的表中仅保存当前类中定义的属性,不包含父类的属性。
优点:
- 不存在冗余。
- 可以对每个属性进行单独配置。
- 维护方便,对每个类的修改只需要修改其所对应的表。
缺点:
- 查询时需要进行连接,性能不如另外两个。
有关继承映射
的更多内容,可以自行搜索。
SQLite处理
对于.Net
平台,有许多的SQLite帮助程序库,可以简化SQLite的操作。这里在Windows Phone 8
平台上使用相应的帮助库来测试SQLite数据库。有关如何在Windows Phone 8
中使用SQLite数据库,可以查看sqlite-net-wp8
的说明。
创建数据库表
要创建一个数据库表,我们需要定义相对应的实体类,并且使用对应的特性(Attribute
)去修饰相应的类(class
)和属性(Property
)。如下是一个实体类定义:
[Table("base_data")]
public class BaseData
{
[PrimaryKey, AutoIncrement, Column("id")]
public int ID { get; set; }
[Column("name")]
public string Name { get; set; }
[Column("key"), NotNull]
public string Key { get; set; }
}
继承映射?
对于一个实体类,我们需要使用TableAttribute
修饰,并传入表名称。而在查找的时候,同样需要传入要查找的表对应的实体类型,因此,我觉得SQLite-net在对继承映射处理时采用了一种更简单的实现:对于每个具体的实体类都对应一个表,在这张表中会包含有该类所有的(包括基类)具有ColumnAttribute
特性的属性,但是SQLite-net并不提供在继承映射中最重要的一个特性:查询父类时同时返回所有符合条件的子类。
本来SQLite就是一个轻量级的数据库,因此SQLite-net在实现的时候,只是把我们定义的实体类作为一个类似参照的东西,所以对于Table
的修饰也没有要求更多的与继承映射有关的字段。不过这样也好,毕竟一般当我们需要使用类似SQLite的数据库时,也不会有太多的要求,而SQLite-net这样的处理,一般并不会影响我们对数据的处理。
分析 - 源代码
为了验证SQLite-net对具有继承关系的实体类的处理方式,我们可以查看它的源代码。而与SQLite-net对类的处理方式直接相关联的就是那几个Attribute
了,从MSDN
中可以查到,对于一个Attribute
特性,可以使用AttributeUsageAttribute
类来指示其使用方法,该类的声明如下:
/// <summary>
/// 指定另一特性类的用法。无法继承此类。
/// </summary>
public sealed class AttributeUsageAttribute : System.Attribute
{
/// <summary>
/// 用指定的 <see cref="System.AttributeTargets"/>、
/// <see cref="System.AttributeUsageAttribute.AllowMultiple"/> 值和
/// <see cref="System.AttributeUsageAttribute.Inherited"/> 值列表初始化
/// System.AttributeUsageAttribute 类的新实例。
/// </summary>
/// <param name="validOn"> 使用按位 OR 运算符组合的一组值,用于指示哪些程序元素是有效的。</param>
public AttributeUsageAttribute(System.AttributeTargets validOn);
/// <summary>
/// 获取一组值,这组值标识指示的特性可应用到的程序元素。
/// </summary>
public System.AttributeTargets ValidOn { get; private set; }
/// <summary>
/// 获取或设置一个布尔值,该值指示能否为一个程序元素指定多个指示特性实例。
/// </summary>
public bool AllowMultiple { set; get; }
/// <summary>
/// 获取或设置一个布尔值,该值指示指示的特性能否由派生类和重写成员继承。
/// </summary>
public bool Inherited { set; get; }
}
我们可以指定一个Attribute
的使用范围(使用AttributeTargets
枚举,例如类、属性、方法、参数等),还可以指定该特性能否在一个元素上重复使用(使用AllowMultiple
属性),而最后一个Inherited
属性则是我们关注的重点,该值指示这个特性能否被继承,也就是在基类的某个元素中定义的特性,在子类中是否有效,这个属性的默认值为true
。
而在SQLite.cs
文件中,我们可以找到TableAttribute
、ColumnAttribute
等的定义:
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : System.Attribute { ... }
[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute : System.Attribute { ... }
从定义中可以看出,这些特性都可以被继承,因此对于一个子类来说,基类中的数据库表字段它也会拥有,这就简化了我们对实体类的定义,我们可以按照一般的对象抽象方式,为实体类创建相应的继承关系。例如我们可以把一些在所有的数据库表中都存在的字段移到一个公共基类中去定义,而且这样做,以后我们对数据库进行操作时,也可以利用面向对象的多态来进行处理。
分析 - 调试
实验是检验真理的唯一标准。为了更明确的观察SQLite-net对继承的实体类的处理,我们可以构建相应的测试程序。
在这里,首先定义如下的测试数据库实体类:
public class Data
{
[PrimaryKey, AutoIncrement, Column("id")]
public int ID { get; set; }
}
[Table("base_data")]
public class BaseData : Data
{
[Column("name")]
public string Name { get; set; }
[Column("key_value")]
public string KeyValue { get; set; }
public override string ToString()
{
return string.Format("ID: {0}; Name: {1}; KeyValue: {2}", ID, Name, KeyValue);
}
}
[Table("child_data")]
public class ChildData : BaseData
{
[Column("list")]
public string List { get; set; }
[Column("date")]
public DateTime? Date { get; set; }
[Column("bool_null")]
public bool? BoolNull { get; set; }
public override string ToString()
{
return string.Format("{0}\nList: {1}; Date:{2}; Bool: {3}",
base.ToString(), List, Date, BoolNull);
}
}
然后再应用中创建数据库并向两个表中填充一些数据。之后在进行查询操作,通过select * from table
列出数据库表中的所有数据,从查询结果可以看出,对于基类BaseData
所对应的数据库表的查询并不会返回其子类所对应的表中的数据。
结论
SQLite-net并没有完整的实现一种继承映射,但是依靠.Net
中的Attribute
,使得我们在为数据库创建实体类的时候,完全可以按照一般的分析分析方法,而对数据库的操作,在SQLite-net中也是反映到对类的操作上,因此我们也可以完全的利用面向对象中的各种特性,例如多态。