2013-04-18 11 views
9

Powiedzmy mam dwa różne typy zarówno na samej tabeli bazy danych (pojedynczy dziedziczenia tabela):sqlalchemy: Konwersja odziedziczony typ z jednego do innego

class Employee(db.Model): 
    id = db.Column(db.Integer, primary_key = True) 
    name = db.Column(db.String, nullable = False) 
    discriminator = db.Column('type', String) 
    __mapper_args__ = {'polymorphic_on': discriminator} 

class Manager(Employee): 
    __mapper_args__ = {'polymorphic_identity': 'manager'} 
    division = db.Column(db.String, nullable = False) 
    role = db.Column(db.String, nullable = False) 

class Worker(Employee): 
    __mapper_args__ = {'polymorphic_identity': 'worker'} 
    title = db.Column(db.String, nullable = False) 

(Tak, używam skrzynkowego sqlalchemy i nie zwykła wanilia) Teraz, jak mogę przejść do konwersji jednego modelu deklaratywnego na inny. To znaczy, co jeśli "pracownik" został awansowany na "menedżera"? Jak mogę to zrobić? Czy muszę napisać surowy SQL, aby to zrobić?

Przepraszam, jeśli zostało to wcześniej zadane, ale nie mogłem go znaleźć z Google. Pamiętaj, że jest to przykładowy przykład.

+0

Myślę, że zamiast pisać nagi SQL, wystarczy wykonać wystąpienie menedżera i skopiować odpowiednie właściwości, a następnie usunąć oryginalnego pracownika. Ale może to być trudne w zależności od tego, ile jest obcych kluczy. – Hannele

Odpowiedz

9

To kludgy, a to powoduje, ostrzeżenie, ale można brute-force modyfikować kolumny dyskryminatora poprzez ustawienie właściwości:

john_smith = session.query(Employee).filter_by(name='john smith').one() 
john_smith.discriminator = 'manager' 
session.commit() 

spowoduje to ostrzeżenie jak,

SAWarning: Flushing object <Worker at 0xdeadbeef> with incompatible polymorphic 
identity 'manager'; the object may not refresh and/or load correctly 
    mapper._validate_polymorphic_identity(mapper, state, dict_) 

Ty można to po prostu zignorować, o ile usunie się problemy, które spowoduje. Najbezpieczniejszym zadaniem jest zamknięcie sesji() lub usunięcie z niej wszystkiego() zaraz po zatwierdzeniu.

Jeśli musisz, możesz naprawić problemy z obiektem Johna, po prostu usuwając Johna z sesji (session.expunge(john_smith)). Musisz być z tym ostrożny; wszelkie pozostałe odniesienia do john_smith zatrzymają obiekt, chociaż na szczęście zostanie on odłączony od session i nie będzie można z nim nic zrobić.


Wypróbowałem także inne oczywiste opcje. Ani działało, ale oba są ilustracją jakich Session sklepach obiektów sqlalchemy i jak:

  1. session.refresh(john_smith) nie z

    InvalidRequestError: Could not refresh instance '<Worker at 0xdeadbeef>' 
    

    to dlatego SQLAlchemy odpytuje bazę dla Worker (nie Employee) i może” • znaleźć kogoś o nazwisku John Smith, ponieważ baza danych wie, że John został awansowany ze względu na nową, fantazyjną wartość w jego kolumnie type.

  2. session.expire(john_smith) powiedzie się, ale nie aktualizuje Jana jako nowej klasy, a każda następna dostęp do niego będzie skutkować

    ObjectDeletedError: Instance '<Worker at 0xdeadbeef>' has been deleted, or 
    its row is otherwise not present. 
    

    SQLAlchemy nadal uważa John jest Worker i próbuje zapytać o nim jako Worker. To dlatego, że on wciąż utrzymywały się w session.identity_map, który wygląda tak:

    {(saexample2.Employee, (1,)): <saexample2.Worker at 0xdeadbeef>} 
    

    Więc nie Jan, wyraźnie wymienione jako Worker obiektu. Gdy wykonasz expunge() John z sesji, ten wpis w słowniku zostanie wyczyszczony. Kiedy go przetworzysz, wszystkie jego zmapowane właściwości zostaną oznaczone jako nieaktualne, ale nadal istnieje w słowniku.

+0

To jest dobrze przemyślana, opisowa odpowiedź. Dziękuję Ci. – wheaties

1

Proponuję przerobienie Twojego modelu obiektowego. Znakiem, że model obiektowy skorzystałby z ponownego przemyślenia, jest sytuacja, w której jeden obiekt działa równie dobrze jak atrybut innego. W takim przypadku Worker.title może równie dobrze być "Menedżerem".

Ponadto Manager.division działa lepiej jako własna jednostka Podział. Nie tylko dlatego, że Dywizja miałaby jeden do wielu relacji z Robotnikiem.

Coś jak być może obiekt Division z menedżerem ForeignKey wskazującym na obiekt Employee. Obiekt Employee miałby atrybut title; w Employee.__init__() można ręcznie sprawdzić, czy pracownik jest kierownikiem dowolnych działów, a następnie ustawić Employee.title na "Menedżer" z __init__().

+0

To dobra sugestia w ogóle. Jednak, chociaż mój przykład jest wymyślony, nadal muszę wiedzieć, jak to zrobić, ponieważ moje obiekty nie są ze sobą powiązane za pomocą kolumny tożsamości. – wheaties

Powiązane problemy