为SQLAlchemy多对多数据库设置关系/映射

2 投票
4 回答
2141 浏览
提问于 2025-04-15 19:37

我刚接触SQLAlchemy和关系数据库,想为一个带注释的词典建立一个模型。我希望能够支持任意数量的键值注释,这些注释可以在运行时添加或删除。由于键的名称会有很多重复,我不想直接使用这个解决方案,虽然代码是相似的。

我的设计包含词对象和属性对象。词和属性分别存储在不同的表中,还有一个property_values表来连接这两者。下面是我的代码:

from sqlalchemy import Column, Integer, String, Table, create_engine
from sqlalchemy import MetaData, ForeignKey
from sqlalchemy.orm import relation, mapper, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite:///test.db', echo=True)
meta = MetaData(bind=engine)

property_values = Table('property_values', meta,
    Column('word_id', Integer, ForeignKey('words.id')),
    Column('property_id', Integer, ForeignKey('properties.id')),
    Column('value', String(20))
)
words = Table('words', meta,
    Column('id', Integer, primary_key=True),
    Column('name', String(20)),
    Column('freq', Integer)
)
properties = Table('properties', meta,
    Column('id', Integer, primary_key=True),
    Column('name', String(20), nullable=False, unique=True)
)
meta.create_all()

class Word(object):
    def __init__(self, name, freq=1):
        self.name = name
        self.freq = freq

class Property(object):
    def __init__(self, name):
        self.name = name
mapper(Property, properties)  

现在我想实现以下功能:

Session = sessionmaker(bind=engine)
s = Session()
word = Word('foo', 42)
word['bar'] = 'yes' # or word.bar = 'yes' ?
s.add(word)
s.commit()

理想情况下,这应该在词表中添加1|foo|42,在属性表中添加1|bar,并在property_values表中添加1|1|yes。但是,我没有正确的映射和关系来实现这个目标。从阅读http://www.sqlalchemy.org/docs/05/mappers.html#association-pattern的文档中,我感觉我想在这里使用一个关联代理或类似的东西,但语法对我来说不太清楚。我尝试了这个:

mapper(Word, words, properties={
    'properties': relation(Property, secondary=property_values)
    })

但是这个映射器只填充了外键值,我还需要填充其他的值。任何帮助都将非常感激。

4 个回答

1

给上面的Brent的评论:

你可以用 session.flush() 来代替 commit(),这样就能在你的模型实例上获取一个 idflush() 会执行必要的SQL操作,但不会真正提交,所以如果需要的话,你可以稍后撤销。

1

这里有一个非常相似的问题,只是界面上有一点不同。不过,通过定义 __getitem____setitem____delitem__ 这几个方法,很容易就能解决这个问题。

1

你可以直接使用基于字典的集合映射,这是一个现成的解决方案,可以解决你的问题。以下是链接中的摘录:

from sqlalchemy.orm.collections import column_mapped_collection, attribute_mapped_collection, mapped_collection

mapper(Item, items_table, properties={
    # key by column
    'notes': relation(Note, collection_class=column_mapped_collection(notes_table.c.keyword)),
    # or named attribute
    'notes2': relation(Note, collection_class=attribute_mapped_collection('keyword')),
    # or any callable
    'notes3': relation(Note, collection_class=mapped_collection(lambda entity: entity.a + entity.b))
})

# ...
item = Item()
item.notes['color'] = Note('color', 'blue')
print item.notes['color']

或者你可以试试在SQLAlchemy中插入多对多关系的数据的解决方案。显然,你需要把list的逻辑换成dict的逻辑。
可以请提问者把他最终使用的代码发出来,里面提到他用了associationproxy

撰写回答