Предполагая модель отношений «многие ко многим» в sqlalchemy с mysql, как обновить данные, игнорируя дубликаты?

Я немного застрял в sqlalchemy, пытаясь обновить некоторые данные.

У меня есть отношения многие ко многим и один ко многим. Во-первых, это связь между автором и возможным написанием его имени. Во-вторых, связывает авторов с их письменной литературой. У статьи может быть несколько авторов и наоборот.

Предположим, что автор «Питер Шоу», у которого уже есть 4 статьи, сохраненные и связанные с ним в базе данных. Нет, я хочу "добавить" новый набор из 6 работ для "Питер Шоу". К сожалению, 4 из 6 статей уже хранятся в базе данных. Вот почему session.commit() приводит к повторяющейся ошибке.

Есть ли общий способ избежать повторяющихся ошибок и указать sqlalchemy просто заполнить дыры, а не жаловаться на дубликаты? Ни документ sqlalchemy, ни Google не смогли просветить меня явным ответом/подходом, поэтому любые предложения приветствуются.

Вот модели, с которыми я тестирую:

class NameSpelling(Base):
    __tablename__ = 'name_spellings'

    id = Column(Integer, primary_key=True)
    name = Column(String(255), nullable=False, unique=True, index=True)
    authors_id = Column(Integer, ForeignKey('authors.id'))

    def __init__(self, name=None):
        self.name = name

    def __repr__(self):
        return "NameSpelling(%r)" % (self.name)

class Author(Base):
    __tablename__ = 'authors'

    id = Column(Integer, primary_key=True)
    name = Column(String(255), nullable=True, unique=True, index=True)

    papers = relationship('Paper',
                          secondary=author_paper,
                          backref='authors')

    name_spellings = relationship(NameSpelling,
                                  order_by=NameSpelling.id,
                                  backref="author",
                                  cascade="all, delete, delete-orphan")

    def __init__(self, name=None):
        self.name = name

    def __repr__(self):
        return "Authors(%r, %r)" % (self.name_spellings, self.name)


class Paper(Base):
    __tablename__ = 'papers'

    id = Column(Integer, primary_key=True)
    title = Column(String(1500), nullable=False, index=True)
    url = Column(String(255), nullable=False, unique=True, index=True)
    date = Column(Date(), nullable=True)

    def __init__(self, title=None, url=None, date=None):
        self.title = title
        self.url = url
        self.date = date

    def __repr__(self):
        return "Paper(%r)" % (self.title)

person Aufwind    schedule 01.07.2011    source источник


Ответы (1)


У меня точно такая же проблема с проектом SQLAlchemy. Что я в итоге сделал (и что, вероятно, является плохим способом решения проблемы), так это проверить коллекции отношений перед добавлением нового экземпляра в сеанс и заменить связанные экземпляры результатом session.merge(), если таковые имеются .

Это выглядит примерно так:

def add_instance_to_session(instance, session):
    '''
    Add instance to session, while checking for existing child instances in
    the relationship collection instance.child_list.
    '''
    def _merge_and_replace(child):
        with session.no_autoflush:
            merged_child = session.merge(child)
            if id(merged_child) != id(child):
                try:
                    session.expunge(child)
                except sqlalchemy.exc.InvalidRequestError:
                    # child wasn't in the session to begin with
                    pass
                return merged_child
            else:
                return child
    instance.child_list = map(_merge_and_replace, instance.child_list)
    session.add(instance)

Кажется, это работает для меня, но выглядит довольно плохо с точки зрения производительности, особенно если у вас много детей. Возможно, есть лучший способ использования идиомы ON DUPLICATE KEY, которую предлагает mySQL, или подобных конструкций.

[править] Часть session.expunge(), вероятно, не нужна, если только описанный выше метод используется для добавления экземпляров в сеанс, поскольку дочерние элементы не могут быть в сеансе в этот момент. По крайней мере, я так думаю...

person dorian    schedule 30.03.2012