邮件列表中我连发了数封信报告了我发现的错误,并给出了我认为的解决方案,但无人响应。看一看订阅的人数不到100,而且在邮件列表上的日期是半夜……
对象关系
说正事吧。前面我们已经了解了基本的数据映谢或ORM的一些东西,知道了一些基本的操作。但这远远是不够的。许多ORM模块还提供对象与对象间的关系的实现,象 OneToOne, ManyToOne, ManyToMany。其实不用这些东西也完全没有问题,这就是一个多表查询的问题,使用SQL直接操作是非常简单而且直观。在 SQLAlchemy 的文档中,在讲述 Data Mapping 之前就有许多这样的例子,如果你喜欢这种方式,不妨看一看。但现在我们还是学一学对象间的关系的实现吧,管它有用没用,先学着再说。其实很简单的原因:就是为了方便。不过学起来却不一定轻松,还会带来一些新问题。
首先修改一下 Table Metadata 为:
RssCategory = Table(‘rss_category’, sqlite_engine,
Column(‘id’, Integer, primary_key = True),
Column(‘title’, String, nullable = False)
)
RssFeed = Table(‘rss_feed’, sqlite_engine,
Column(‘id’, Integer, primary_key = True),
Column(‘category_id’, Integer, ForeignKey("rss_category.id")),
Column(‘title’, String, nullable = False),
Column(‘link’, String),
Column(‘description’, String)
)
注意 RssFeed 是一个新表,因此你应该要创建一下,而 RssCategory 已经创建好了。这里很重要的一点是外键的定义:ForeignKey("rss_category.id")。这是 SQLAlchemy 的缺省关联对象要使用的。当然也可以不指定外键,而显式地指明关联的字段。同时要注意,外键中的参数是真正的表名和字段,而不是Table对象。
既然是学习 ORM ,则必然要定义两个对应的类,为了创建方便,增加相应的 __init__方法。
class Cate(object):
def __init__(self, title=None):
self.title = title
class Feed(object):
def __init__(self, title=None, link=None, description=None):
self.title = title
self.link = link
self.description = description
这里都忽略了主键和外键的生成,这些事情由 SQLAlchemy 关心即可。
Table Metadata 和 user-defined class 都有了,mapper如何定义呢?使用下面的方法定义:
m = mapper(Cate, RssCategory, properties = {
‘feeds’ : relation(Feed, RssFeed)
}
)
这里多了一个 properties 参数,它是一个字典值。我增加了一个 feeds 值,它又是一个 relation 对象,这个对象实现了 Feed 类与RssFeed的映射。这样我通过RssCategory的feeds属性就可以反映出RssFeed的值来。
这里还要说明,我报告的错误之所以会产生主要是因为在后面的测试中我通过reflect来实现 Table Metadata 的创建,而不是真正我定义的代码。因此上还是有一些区别。特别是在测试上面映射代码时,报了一个错,说是找不到外键的关系。而我查看了生成表的 SQL 语句,其中对于外键的定义是使用REFERENCE这个名字,并且在使用 reflect 得到 Table Metadata 时,根本就没有外键信息。看到以后还是使用 Table Metadata 的好,使用reflect可能就会有问题。
关键定义好了,让我们按文档那样插入数据试试吧:
c = Cate(‘New Category’)
c.feeds.append(Feed(‘One’, ‘http://one.com’, ‘descript one’))
c.feeds.append(Feed(‘Two’, ‘http://two.com’, ‘descript two’))
objectstore.commit()
成功了。下面再取出数据看一看结果:
for i in m.select():
print i.id, i.title
for j in i.feeds:
print j.id, j.title, j.link, j.description
结果是:
1 New Category
1 One http://one.com descript one
2 Two http://two.com descript two
成功,没有问题。
使用关系就是方便,可以从一个对象直接找到相对应的其它的对象,而SQL的方式虽然直观,但不够面向对象,各有所长。
在文档中还举了一个del u.addresses[1]的例子。从这个例子可以看出,删除u.addresses[1]并不是真正将对应的 addresses 表记录删除,它只是将 addresses 相应的记录的外键字段 user_id 置为了 None。这样就表明这条记录不再与任何 User 记录有关系。那么这条记录虽然存在,但是已经没有什么用了。为了避免这个无用的数据,可以在进行Mapping时再加入 private=True 参数,如:
m = mapper(Cate, RssCategory, properties = {
‘feeds’ : relation(Feed, RssFeed, private=True)
}
)
这里与 SQLObject 有所不同。在 SQLObject 中关系是一个单独的表,它保存着有关系的表的id对照。这样象我的例子,有两个表,再加上关系表,实际上在数据库中是三个表。而这里仍然是两个表。那么对照表就是由 RssFeed 本身来实现的,因为它多了一个外键字段,而这个字段是存在于 RssFeed 表中的。这样RssFeed 表即有自身的id还有RssCategory的id,因此起到了和 SQLObject 的关系表一样的作用。(这样的话表是少了,但如果关系复杂,会不会照成表字段非常多呢?很乱呢?)
在上面指定了 private=True 之后,它并不是简单的不显示没有对应的记录,而是在删除关系时自动会将关联的记录删除。