【问题标题】:Setting up relations/mappings for a SQLAlchemy many-to-many database为 SQLAlchemy 多对多数据库设置关系/映射
【发布时间】:2011-01-19 23:12:27
【问题描述】:

我是 SQLAlchemy 和关系数据库的新手,我正在尝试为带注释的词典建立一个模型。我想为可以在运行时添加或删除的单词支持任意数量的键值注释。由于键名会有很多重复,我不想直接用this solution,虽然代码差不多。

我的设计有单词对象和属性对象。单词和属性存储在单独的表中,其中包含链接两者的 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 添加到 words 表,将 1|bar 添加到 properties 表,并将 1|1|yes 添加到 property_values 表。但是,我没有正确的映射和关系来实现这一点。我从http://www.sqlalchemy.org/docs/05/mappers.html#association-pattern 的文档中了解到我想在这里使用关联代理或类似的东西,但是我不清楚语法。我对此进行了实验:

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

但是这个映射器只填写外键值,我还需要填写其他值。任何帮助将不胜感激。

【问题讨论】:

    标签: python orm sqlalchemy


    【解决方案1】:

    very similar question,界面略有不同。但是很容易通过定义__getitem____setitem____delitem__ 方法来修复它。

    【讨论】:

    • 感谢您的指点。我有类似的东西,但我想将键名保留为外键,因为键中会有很多重复,我不想让所有这些字符串在数据库中重复。我正在考虑将您的解决方案与上面 [stackoverflow.com/questions/2310153/…. 的 van 解决方案相结合
    【解决方案2】:

    只需使用Dictionary-Based Collections mapping 映射 - 即可解决您的问题。摘自链接:

    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']
    

    或尝试Inserting data in Many to Many relationship in SQLAlchemy 的解决方案。显然,您必须将 list 逻辑替换为 dict 逻辑。
    让问题作者用associationproxy 发布他的最终代码,他提到他最后使用了。

    【讨论】:

    • 这是最简单的方法,但是我的很多条目都会有相同的键,所以我想将这些键唯一化并将它们存储在一个单独的表中。我正在查看您发布的第二个解决方案。
    • 很公平。但是是什么阻止您使用基于字典的集合映射到对象上的属性,但除此之外仅在对象上提供类似 dict 的接口,这基本上是该属性的代理(委托)?
    • 我认为这基本上是我在解决方案中提出的(发布在下面)。
    【解决方案3】:

    我最终将 Denis 和 van 的帖子组合在一起形成了解决方案:

    from sqlalchemy import Column, Integer, String, Table, create_engine
    from sqlalchemy import MetaData, ForeignKey
    from sqlalchemy.orm import relation, mapper, sessionmaker
    from sqlalchemy.orm.collections import attribute_mapped_collection
    from sqlalchemy.ext.associationproxy import association_proxy
    from sqlalchemy.ext.declarative import declarative_base
    
    meta = MetaData()
    Base = declarative_base(metadata=meta, name='Base')
    
    class PropertyValue(Base):
        __tablename__ = 'property_values'
        WordID = Column(Integer, ForeignKey('words.id'), primary_key=True)
        PropID = Column(Integer, ForeignKey('properties.id'), primary_key=True)
        Value = Column(String(20))
    
    def _property_for_name(prop_name):
        return s.query(Property).filter_by(name=prop_name).first()
    
    def _create_propval(prop_name, prop_val):
        p = _property_for_name(prop_name)
        if not p:
            p = Property(prop_name)
            s.add(p)
            s.commit()
        return PropertyValue(PropID=p.id, Value=prop_val)
    
    class Word(Base):
        __tablename__ = 'words'
        id = Column(Integer, primary_key=True)
        string = Column(String(20), nullable=False)
        freq = Column(Integer)
        _props = relation(PropertyValue, collection_class=attribute_mapped_collection('PropID'), cascade='all, delete-orphan')
        props = association_proxy('_props', 'Value', creator=_create_propval)
    
        def __init__(self, string, freq=1):
            self.string = string
            self.freq = freq
    
        def __getitem__(self, prop):
            p = _property_for_name(prop)
            if p:
                return self.props[p.id]
            else:
                return None
    
        def __setitem__(self, prop, val):
            self.props[prop] = val
    
        def __delitem__(self, prop):
            p = _property_for_name(prop)
            if p:
                del self.props[prop]
    
    class Property(Base):
        __tablename__ = 'properties'
        id = Column(Integer, primary_key=True)
        name = Column(String(20), nullable=False, unique=True)
    
        def __init__(self, name):
            self.name = name
    
    engine = create_engine('sqlite:///test.db', echo=False)
    Session = sessionmaker(bind=engine)
    s = Session()
    meta.create_all(engine)
    

    测试代码如下:

    word = Word('foo', 42)
    word['bar'] = "yes"
    word['baz'] = "certainly"
    s.add(word)
    
    word2 = Word('quux', 20)
    word2['bar'] = "nope"
    word2['groink'] = "nope"
    s.add(word2)
    word2['groink'] = "uh-uh"
    del word2['bar']
    
    s.commit()
    
    word = s.query(Word).filter_by(string="foo").first()
    print word.freq, word['baz']
    # prints 42 certainly
    

    数据库的内容是:

    $ sqlite3 test.db "select * from property_values"
    1|2|certainly
    1|1|yes
    2|3|uh-uh
    $ sqlite3 test.db "select * from words"
    1|foo|42
    2|quux|20
    $ sqlite3 test.db "select * from properties"
    1|bar
    2|baz
    3|groink
    

    【讨论】:

    • in _create_propval 你真的需要添加/提交新的属性对象吗?当您稍后提交会话时,它不会自动添加/提交吗?如果是这样,那么我不会在那里添加/提交,因为您可能会回滚会话而不是提交
    • 是的,这是代码的粗略派对。我需要 Property 对象来获取一个 id,以便在创建 PropertyValue 时可以引用它(因为 PropertyValues 使用外键来引用属性名称)。在提交之前,该属性不会获得其 ID。有什么好方法可以在不提交的情况下获取 id?
    • 记录一下,解决方法是将_create_propval中的s.commit()行替换为s.merge(p)。
    【解决方案4】:

    以上对布伦特原油的评论:

    您可以使用session.flush() 而不是commit() 在模型实例上获取idflush() 将执行必要的 SQL,但不会提交,因此您可以稍后根据需要回滚。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-04-07
      • 2020-09-18
      • 2014-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多