使用中介表处理属性映射到列表的SQLAlchemy问题

108 阅读2分钟

在使用SQLAlchemy时,我们有时需要将属性映射到列表。例如,我们有一个Parent表和一个Child表,Parent表中存储父节点的信息,Child表中存储子节点的信息。我们希望在Parent对象中有一个children属性,该属性的值是一个列表,其中包含所有属于该父节点的子节点。

  1. 解决方案

1663724455093.jpg 为了实现这一目标,我们需要使用一个中介表ParentChildParentChild表中存储父节点和子节点的关联关系,以及子节点的序号。然后,我们可以使用SQLAlchemy的relation()函数来建立ParentParentChild表之间的关系,并使用collection_class参数来指定我们自己的集合类。

这里是一个示例代码:

from sqlalchemy import *
from sqlalchemy.orm import mapper, relation, sessionmaker
from sqlalchemy.orm.collections import collection

metadata = MetaData()

parent = Table('parent', metadata,
    Column('parent_id', Integer, primary_key=True),
    Column('name', Unicode),
)

child = Table('child', metadata,
    Column('child_id', Integer, primary_key=True),
    Column('name', Unicode),
)

parent_child = Table('parent_child', metadata,
    Column('parent_id', Integer, ForeignKey(parent.c.parent_id)),
    Column('child_id', Integer, ForeignKey(child.c.child_id)),
    Column('number', Integer),
    PrimaryKeyConstraint('parent_id', 'child_id'),
)

class ParentChild(object):
    def __init__(self, child, number):
        self.child = child
        self.number = number

class Parent(object): pass

class Child(object): pass


class MyMappedCollection(object):

    def __init__(self, data=None):
        self._data = data or {}

    @collection.appender
    def _append(self, parent_child):
        l = self._data.setdefault(parent_child.number, [])
        l.append(parent_child)

    def __setitem__(self, number, child):
        self._append(ParentChild(number=number, child=child))

    def __getitem__(self, number):
        return tuple(pc.child for pc in self._data[number])

    @collection.remover
    def _remove(self, parent_child):
        self._data[parent_child.number].remove(parent_child)

    @collection.iterator
    def _iterator(self):
        for pcs in self._data.itervalues():
            for pc in pcs:
                yield pc

    def __repr__(self):
        return '%s(%r)' % (type(self).__name__, self._data)


mapper(Parent, parent, properties={
     'children': relation(ParentChild, collection_class=MyMappedCollection),
})
mapper(Child, child)
mapper(ParentChild, parent_child, properties={
    'parent': relation(Parent),
    'child': relation(Child),
})

engine = create_engine('sqlite://')
db = sessionmaker(bind=engine)()
metadata.create_all(bind=engine)

p = Parent()
http://www.jshk.com.cn/mb/reg.asp?kefu=xiaoding;//爬虫IP免费获取;
c1 = Child()
c2 = Child()
c3 = Child()
p.children[1] = c1
p.children[1] = c2
p.children[2] = c3

db.add(p)
db.commit()
p_id = p.parent_id
db.expunge_all()

p = db.query(Parent).get(p_id)
print p.children[1]
print p.children[2]

输出结果:

(Child(), Child())
(Child(),)

从输出结果中,我们可以看到,Parent对象的children属性是一个列表,其中包含所有属于该父节点的子节点。