2010-02-23 2 views
2

Я новичок в SQLAlchemy и реляционных базах данных, и я пытаюсь создать модель для аннотированной лексики. Я хочу поддерживать произвольное количество аннотаций по ключевым словам для слов, которые могут быть добавлены или удалены во время выполнения. Поскольку в именах клавиш будет много повторений, я не хочу напрямую использовать this solution, хотя код аналогичен.Настройка отношений/сопоставлений для базы данных «многие-ко-многим» 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 в таблицу свойств, а также добавить 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) 
    }) 

но картографа заполняет только в иностранных ключевых ценностей, и мне нужно, чтобы заполнить другое значение, а также. Будем очень благодарны любой помощи.

ответ

1

Просто используйте 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, о котором он упомянул в конце.

+0

Это самый простой способ, но многие мои записи будут иметь одинаковые ключи, поэтому я хочу использовать эти ключи и хранить их в отдельной таблице. Я смотрю второе решение, которое вы разместили. –

+0

достаточно справедливо. но что мешает вам использовать сопоставление коллекции на основе словаря в свойстве на вашем объекте, но в дополнение к этому только предоставить интерфейс типа dict для объекта, который будет в основном быть прокси (делегировать) этому свойству? – van

+0

Я думаю, что это в основном то, что я придумал в своем решении (опубликовано ниже). –

1

Существует very similar question с небольшой разностью интерфейсов. Но его легко исправить, определяя методы __getitem__, __setitem__ и __delitem__.

+0

Спасибо за указатель.У меня было что-то подобное, но я хотел бы сохранить имена ключей в качестве внешних ключей, потому что в ключах будет много повторений, и я не хочу, чтобы все эти строки дублировались в базе данных. Я смотрю на сочетание вашего решения с решением ванны выше [http://stackoverflow.com/questions/2310153/inserting-data-in-many-to-many-relationship-in-sqlalchemy/2310548#2310548]. –

0

Я в конечном итоге комбинируя сообщения Денис и фургона вместе, чтобы сформировать решение:

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 
+1

в _create_propval действительно ли вам нужно добавить/зафиксировать новый объект Property? не будет ли оно добавлено/выполнено автоматически, когда вы совершите сеанс позже? Если это так, то я бы не добавил/зафиксировал там, потому что вы можете отменить сеанс вместо того, чтобы совершать – van

+0

Да, это отрывочная сторона кода. Мне нужен объект Property, чтобы получить идентификатор, чтобы я мог ссылаться на него при создании PropertyValue (потому что PropertyValues ​​использует внешние ключи для ссылки на имена свойств). Имущество не получает свой идентификатор до его совершения. Любой хороший способ получить идентификатор без совершения? –

+0

Для записи решение заключается в замене строки s.commit() в _create_propval на s.merge (p). –

1

Комментарий для Брента, выше:

Вы можете использовать session.flush() вместо commit(), чтобы получить id на примерах моделей. flush() выполнит необходимый SQL, но не будет зафиксирован, поэтому при необходимости вы можете откатить позже.

Смежные вопросы