2013-04-18 9 views
9

Diciamo che ho due tipi differenti entrambi sulla stessa tabella di database (sola tabella di successione):SQLAlchemy: Convertire tipo ereditato da uno all'altro

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) 

(Sì, sto usando Flask-SQLAlchemy e non plain vanilla) Ora, come potrei fare per convertire un modello dichiarativo in un altro. Cioè, cosa succede se un "Lavoratore" è stato promosso a "Manager?" Come lo faccio? Devo scrivere SQL raw per farlo?

Scusate se questo è stato chiesto prima, ma non sono riuscito a trovare dal Googles. Si prega di notare, questo è un esempio forzato.

+0

Penso piuttosto che scrivere SQL nuda, basta creare un'istanza di Manager e copiare tutte le proprietà pertinenti, quindi eliminare il Worker originale. Ma potrebbe essere difficile a seconda di quante chiavi esterne sono coinvolte. – Hannele

risposta

9

E 'kludgy, e provoca un avvertimento, ma è possibile la forza bruta modificare la colonna discriminatore impostando la proprietà:

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

questo causerà un avvertimento simile,

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_) 

È posso semplicemente ignorarlo, a patto di risolvere i problemi che causerà. L'argomento più sicuro è chiudere la sessione (session.close()) o cancellare tutto da esso (session.expunge_all()) immediatamente dopo il commit.

Se è necessario, è possibile risolvere i problemi con l'oggetto di John solo espungendo John dalla sessione (session.expunge(john_smith)). Devi stare attento con quello; eventuali riferimenti rimanenti a john_smith manterranno l'oggetto, anche se per fortuna sarà staccato da session e non sarà permesso fare nulla con loro.


Ho provato anche le altre opzioni ovvie. Nessuno dei due ha funzionato, ma entrambi sono illustrativo di ciò che Session negozi di oggetti di SQLAlchemy e come:

  1. session.refresh(john_smith) fallisce con

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

    Questo perché SQLAlchemy interroga il database per un Worker (non un Employee) e puo' Trovarne uno con il nome di John Smith, perché ora il database sa che John è stato promosso a causa del nuovo fantastico valore nella sua colonna type.

  2. session.expire(john_smith) riesce, ma non riesce ad aggiornare John come una nuova classe, e ogni successivo accesso a lui si tradurrà in

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

    SQLAlchemy pensa ancora John è un Worker, e cerca di query per lui come un Worker. Ecco perché è ancora persisteva nel session.identity_map, che assomiglia a questo:

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

    Quindi c'è Giovanni, indicato esplicitamente come oggetto Worker. Quando si expunge() John dalla sessione, questa voce nel dizionario viene eliminata. Quando lo si esegue, tutte le sue proprietà mappate vengono contrassegnate come obsolete, ma esistono ancora nel dizionario.

+0

Questa è una risposta ben pensata e descrittiva. Grazie. – wheaties

1

Io suggerirei di rielaborare il vostro modello a oggetti. Un segno che il modello a oggetti trarrebbe beneficio da un ripensamento è, quando un oggetto funziona altrettanto bene come l'attributo di un altro. In questo caso, Worker.title potrebbe anche essere "Manager".

Inoltre, Manager.division funziona meglio come il proprio oggetto Divisione. Non ultimo perché una Divisione avrebbe probabilmente una relazione uno a molti con il Lavoratore.

Qualcosa come forse un oggetto Division con un ForeignKey di manager che punta a un oggetto Employee. L'oggetto Employee avrebbe un attributo titolo; nel Employee.__init__() è possibile verificare manualmente se il dipendente è responsabile di alcuna area e quindi impostare il Employee.title a "Manager" da __init__().

+0

Questo è un buon suggerimento in generale. Tuttavia, mentre il mio esempio è artificioso, ho ancora bisogno di sapere come farlo poiché i miei oggetti non sono molto correlati tramite una colonna di identità. – wheaties

Problemi correlati