42

Ho un progetto ASP.NET MVC3 che utilizza Entity Framework 4.3 con l'approccio code-first. Uso le migrazioni per mantenere aggiornato il database.Come gestire le migrazioni in un progetto con più filiali?

Il progetto è sotto controllo sorgente e ho un numero di filiali. Quello che ho appena capito è che ci sarà un problema quando voglio unire uno dei miei rami nel master. Dal momento che ho creato i file di migrazione in entrambi i rami, quando si unirò, si verificheranno migrazioni sovrapposte, che probabilmente causeranno conflitti.

Esiste un buon modo per gestire le Migrazioni in un progetto con più diramazioni?

Aggiornamento

Un modo sarebbe quello di unire, quindi eliminare tutti i migratori-file creati mentre i rami erano separati, e quindi creare un nuovo file di migrazione che contiene tutte le modifiche a partire dal momento della filiale è stato creato fino è stato incorporato nuovamente. Ciò funzionerebbe per l'ambiente di sviluppo in cui è possibile scaricare il database e ricostruirlo con tutti i file di migrazione. Il problema sarebbe il live-environment. Poiché non è possibile eseguire il rollback al momento della creazione del ramo senza il rischio di perdere dati, si verificherà un conflitto quando si tenta di utilizzare il nuovo file di migrazione per aggiornare il database attivo.

risposta

14

Penso che la risposta accettata non sia corretta. C'è un much better solution for handling entity framework migration merge conflicts su una domanda simile.

Tutto quello che devi fare dopo un'unione è ripianificare i meta dati della migrazione nel ramo di destinazione. Quello è che non si ripristina il codice su/giù, solo lo stato nel file resx.

add-migration [the_migration_to_rescaffold_metadata_for] 

Questa procedura non riuscirà però se una migrazione diversa nell'unione hanno cambiato il database in modo tale che la migrazione non è più eseguibile o riporta un risultato inatteso. Detto questo, credo che sia un caso molto raro visto che la maggior parte delle migrazioni dovrebbe essere generata automaticamente, o almeno non dipendere da altre tabelle che non sono state modificate nella migrazione stessa. Problema molto secondario ma di cui bisogna essere consapevoli.

Un tale caso potrebbe essere fxp (non ho potuto pensare ad un esempio migliore)

  • Colonna foo è un int e righe contengono [0, 1, 2]

  • migrazione A dal ramo A cambia pippo in booleano (0 diventa falso automaticamente e> 0 diventa vero)

  • La migrazione B dal ramo B cambia foo in stringa. Si aspetta che sia un int ma è un booleano, la migrazione riuscirà comunque. I dati andranno persi da quando è stata creata la migrazione B, le righe conterrebbero ["0", "1", "2"]. Quando la migrazione A ha modificato la colonna in booleana (e lo ha fatto con successo e con risultato previsto), le righe ora contengono ["0", "1", "1"] e la migrazione B avrà un risultato finale diverso da quello osservato in Branch B.

Probabilmente ci sono più casi limite in cui le cose potrebbero andare storte con la soluzione. Ma se il codice di up/down delle migrazioni non dipende da cose modificate da un'altra migrazione nell'unione, dovrebbe funzionare bene per aggiornare semplicemente i metadati nelle migrazioni.

+0

Funziona solo se si sta unendo dal ramo B al ramo A, quindi premendo su master; se il ramo A è già stato inviato al master e distribuito nell'ambiente live, e lo provi dal ramo B, fallirà. –

+0

Prego elaborare "Questo" in "Funziona solo" e "Provalo dal ramo B". Se hai già A in master, puoi unire master a B, rescaffold, quindi spingere B a master. Potresti avere un caso d'uso problematico, elaborare un po 'quello che hai in mente. – oldwizard

+0

C'è una bandiera chiamata "IgnoreChanges" che probabilmente vorresti usare se segui questa rotta –

13

L'unione delle migrazioni è un'attività manuale IMHO. Parte del codice di migrazione viene generata automaticamente e di solito non uniamo il codice generato automaticamente, ma eseguiamo nuovamente l'autogenerazione dopo l'unione.

Fino squadra ADO.NET fornisce qualche raccomandazione avrei seguito semplice principio:

  • Prima di fare l'unione ripristinare il database master alla versione utilizzata prima ramificazione
  • unire i rami
  • Escludi classi di migrazione create dopo la derivazione dall'assieme unito
  • Aggiungere una nuova migrazione per la base di codice unita che eseguirà la migrazione del database nello stato precedente alla diramazione allo stato dopo l'unione dei rami
  • Se le classi di migrazione escluse contengono qualche personalizzazione unirli alla nuova classe di migrazione
  • migrazione
  • Run di migrare il database di versione corrente fusa

Se i rami contenevano più passaggi di migrazione (versione) si perderli e terminerai con due versioni: prima della ramificazione e dopo l'unione.

Edit:

Non funzionerà in ambiente live. Il problema qui sarebbe il processo di sviluppo stesso. Se hai un ambiente live dovresti mantenere intatto il suo ramo (tranne le correzioni di piccoli bug). Se continui lo sviluppo in quel ramo con la distribuzione di produzione e nello stesso tempo costruisci un'altra versione in un ramo separato senza integrazione continua (= le continue operazioni di fusione tornano al ramo principale per integrare il tuo nuovo sviluppo con il codice base principale) hai un grande problema. Penso che le migrazioni in generale non possano gestirlo.

L'unica opzione in tal caso sarebbe probabilmente la rimozione di tutte le migrazioni dalla soluzione unita e l'eliminazione della tabella MigrationHistory dal database.Quindi è possibile abilitare nuovamente le migrazioni nel progetto e aggiungere la migrazione iniziale per utilizzare il database corrente come punto di partenza = non tornare alla versione precedente perché non saranno disponibili informazioni sulle migrazioni precedenti.

+0

Grazie per la risposta! Ho aggiornato la mia domanda con un pensiero simile proprio come hai scritto la tua risposta. Hai qualche idea su come gestire l'ambiente live? Vedi la mia domanda aggiornata per qualche informazione in più su cosa intendo. –

+0

Grazie per i chiarimenti. Nel mio caso, dal momento che sviluppo nuove funzionalità (che non sono ancora pronte per la produzione) in un ramo separato, suppongo che la soluzione sarebbe quella di unire continuamente il ramo principale nel mio ramo separato, finché il ramo separato non sarà pronto per essere unito di nuovo nel maestro. –

+0

Ouch, questo è un grosso danno per noi. Recentemente abbiamo dovuto inviare una "correzione rapida" all'ambiente live che includeva una migrazione per aggiungere una nuova tabella. La migrazione in dev sta migrando da uno stato diverso da quello in diretta. – Chev

2

Considerare l'utilizzo di una diversa libreria di migrazione che non causa questi conflitti, come FluentMigrator o Migrator.NET.

Non credo che le migrazioni EF siano realmente pronte per l'uso generale con le diramazioni & unite - è un sacco di lavoro e troppo facile da fare errori sbagliati.

12

Modifica: un mio collega ha scoperto un modo più semplice per farlo, ho lasciato la mia risposta originale in fondo per completezza.

(MOLTO IMPORTANTE) le migrazioni in ambiente live non devono essere in conflitto con quelle nel ramo attuale, altrimenti è necessario ripetere tutte le migrazioni e risolvere i conflitti di modifica del modello dati manualmente.

  1. ripristinare il database di sviluppo con i dati ambiente vivo
  2. corsa update-database, dovrebbe funzionare migrazioni dal ramo, e si lamentano 'in grado di aggiornare il database in modo che corrisponda alla corrente bla bla modello di ..'
  3. run add-migration MergeBranchBToMaster -ignoreChanges, questo creerà una migrazione vuota.
  4. corsa update-database nuovo
  5. spingere le modifiche

La magia nella fase 3 in sostanza dice EF per shutup sui modelli non corrispondenti, quindi essere molto sicuri che i vostri migrazioni non siano in conflitto con quelli in ambiente reale. Se lo fanno, puoi sempre creare script SQL per forzare le migrazioni mancanti (che in realtà è il metodo preferito).

risposta originale

ho trovato una soluzione abbastanza facile, in base alla risposta del @Ladislav Mrnka. Questo funzionerà con l'ambiente live [1], devi solo fare attenzione a non modificare le migrazioni implementate.

  1. Prima di unione, prendere atto della migrazione si è aggiunto (MyMigration), e la sua precedente migrazione (BaseMigration) rami

  2. unione in git

  3. Aprire Package Manager Console, ed eseguire : UPDATE-DATABASE -TargetMigration: BaseMigration. Questo tornerà il database allo stato prima di qualsiasi delle migrazioni in conflitto vengono applicate

  4. Eliminare la migrazione locale (MyMigration)

  5. Run: UPDATE-DATABASE. Ciò applicherà tutte le migrazioni più recenti fatte in altri rami.

  6. Esecuzione: ADD-MIGRATION MyMigration. Questo genererà di nuovo la tua migrazione locale in base allo stato attuale del database, come git -rebase.

  7. Esecuzione: UPDATE-DATABASE. Aggiorna il database con la tua migrazione locale.

Questo funziona anche se si dispone di più migrazioni locali, ma le unirà tutte in una singola.

[1] lavorando con ambiente live, intendo che la migrazione generata può essere applicata all'ambiente live che potrebbe già avere alcune/tutte le migrazioni degli altri rami applicate. I passaggi stessi sono puramente a scopo di sviluppo.

+1

Come puoi ripristinare un database live? Se hanno usato questo codice base con detta migrazione, il ripristino lascerà l'applicazione in uno stato incoerente ed eventualmente eliminerà i dati dell'utente. – Jack

+0

questi passaggi non sono pensati per essere usati contro l'ambiente live, ho aggiunto note per spiegare cosa significa. –

+0

Questa è la risposta corretta. Tutti gli altri sono stupidi –

3

Ho riflettuto su questo e spero di contribuire alle diverse opinioni e pratiche presentate qui.

Considerare cosa rappresentano effettivamente le migrazioni locali. Quando lavoro localmente con un database di sviluppo, utilizzo le migrazioni per aggiornare il database nel modo più conveniente possibile aggiungendo colonne ecc. Alle tabelle, aggiungendo nuove entità ecc.

Quindi, controllo Add-Migration my modello corrente (let's chiamalo modello b) contro mio modello precedente (modello a) e genera una migrazione per passare da a => b nel database.

A me fa molto poco senso per cercare di unire i miei migrazioni con chiunque migrazioni altre famiglie, se ognuno ha infatti un proprio database e c'è poi esiste un qualche tipo di server stage/test/dev/database di produzione in l'organizzazione. Tutto dipende da come è stato impostato il team, ma ha senso isolarsi a vicenda dalle modifiche apportate da altre persone se si desidera lavorare veramente in modo distribuito.

Bene, se lavori distribuito e hai qualche entità, Persona, per esempio, su cui lavori. Per qualche motivo, molte altre persone ci stanno lavorando. Quindi, aggiungi e rimuovi le proprietà su Persona secondo necessità per la tua particolare storia nello sprint (stiamo lavorando tutti agilmente qui, no?), Come il numero di previdenza sociale che hai trasformato in un intero perché non sei quello luminoso e quindi a una stringa, ecc.

Aggiungere FirstName e LastName.

Si è fatto e si hanno dieci strane migrazioni su e giù (probabilmente ne sono state rimosse alcune mentre si lavorava da quando si trattava di merda) e si recuperano alcune modifiche dal repository Git centrale. Wow. Anche il tuo collega Bob aveva bisogno di alcuni nomi, forse avresti dovuto parlarci?

In ogni caso, ha aggiunto NameFirst e NameLast, credo ... quindi cosa fai? Bene, ti unisci, refactoring, cambia in modo che abbia nomi più sani ... come FirstName e LastName, tu esegui i tuoi test e controlli il suo codice, e poi spinga al centro.

Ma per quanto riguarda le migrazioni? Bene, ora sarebbe il momento di fare una migrazione spostando il repository centrale, o il ramo "test" più specificamente, contenere una piccola migrazione da il suo modello a => modello b. Questa migrazione sarà una e una sola migrazione, non dieci strane.

Vedete cosa sto ottenendo? Stiamo lavorando con bei piccoli pocos e i loro confronti costituiscono le migrazioni attuali. Quindi, non dovremmo fondere affatto le migrazioni, a mio avviso, dovremmo avere migrazioni per ramo o qualcosa del genere.

In effetti, abbiamo persino bisogno di creare la migrazione nel ramo dopo l'unione? Sì, se questo database viene aggiornato automaticamente, è necessario.

Devo lavorare ancora, quelli sono i miei pensieri su questo, almeno.

+0

Questo è davvero un pensiero interessante. Quindi credo che quello che stai dicendo è che i file di migrazione non appartengono affatto al controllo del codice sorgente? –

+0

Un caso d'uso è dove la migrazione contiene una logica di qualche tipo. Se unisci i tuoi pocifo modificati a diversi rami, ciascuno di questi obiettivi dovrà creare migrazioni simili. Cosa succede se dimentichi che parte della migrazione non generata automaticamente? Sono d'accordo però che la maggior parte delle migrazioni vengono create automaticamente e possono essere facilmente create nel ramo di destinazione quando necessario. – oldwizard

0

Penso che ciò che dice @LavaEater abbia molto senso. Sto implementando una strategia di ramificazione (Sviluppo, Main, Release) e allineandola con gli ambienti nello sviluppo, QA e processo di rilascio.

  • Sviluppo ramo - Sviluppo locale
  • ramo principale - unire le modifiche dal ramo di sviluppo e distribuire al mio ambiente di gestione temporanea (un sito web Azure e database SQL)
  • ramo di uscita - Unire le modifiche da Main e distribuire alla produzione ambiente (un altro sito Azure e SQL Database)

mi è venuta in contro il problema discusso in precedenza e, a mio parere le complicazioni intorno migrazioni e le potenziali soluzioni presentano una grande quantità di rischio nel processo di rilascio .L'esecuzione di migrazioni indipendenti in Sviluppo, Principale e Rilascio in modo efficace significa che lo schema che ho incluso nel build in Dev non è lo schema che entra in QA su Staging e lo schema che il QA firma su Staging non è lo schema distribuito su Live (a meno che non segua una delle soluzioni suggerite che sono sicuro che potrebbe funzionare ma potrebbe essere soggetta a errori).

Per echo @LavaEater - qual è il vero vantaggio che ottengo dal codice EF prima? Personalmente, penso che sia la facilità con cui posso generare uno schema dal codice (e potenzialmente modificare le migrazioni generate automaticamente se voglio). Successivamente, le migrazioni sono una complicazione di ciò che dovrebbe essere un semplice processo di distribuzione.

mio pensiero attuale è quella di utilizzare il codice prima per generare le migrazioni in fase di sviluppo e quindi: -

  • Opzione A) - Utilizzare Aggiornamento-Database -script script le modifiche dello schema e metterle sotto controllo della fonte. C'è ancora un potenziale di conflitto se 2 persone stanno modificando lo stesso modello, ma penso che sia più facile da gestire.

  • Opzione B) - Utilizzare qualcosa come SQL Compare per generare script di modifica dello schema. Questo è potenzialmente più flessibile e trasparente poiché mi piace vedere esattamente quali modifiche dello schema sto applicando al mio database di produzione (chiamami paranoico).

Mi manca qualcosa? Immagino che ci sarà qualche configurazione da fare per disabilitare le prime migrazioni di codice nei rami Main e Release (supponendo che il DB verrà creato e aggiornato dagli script). A parte questo, mi sembra una soluzione sicura, ma valuterei un secondo parere.

7

Rowan Miller ha realizzato un ottimo video su questo argomento sul canale 9: Migrations - Team Environments. Si riferisce all'entità quadro 6.

Descrive uno scenario in cui i primi sviluppatori A e B stanno lavorando sullo stesso modello e A controlla prima. Ora lo sviluppatore B deve affrontare i problemi che ha quando ottiene l'ultima versione da A.

Questo è essenzialmente lo stesso come avere conflitti tra rami diversi, perché il problema generale è quello di unire i cambiamenti di migrazione fatto nello stesso tempo ma effettivamente avendo uno stato sorgente diverso del modello.

La soluzione è:

  • Quando la risoluzione dei conflitti del sistema di controllo di versione, gli sviluppatori B deve accettare entrambe le modifiche da se stesso e sviluppatore A.
  • Un comando UpdateDatabase di sviluppatore B sarebbe ancora fallire in questo momento (messaggio di errore: "Impossibile aggiornare il database in modo che corrisponda al modello corrente perché ci sono modifiche in sospeso ...")
  • Lo sviluppatore B deve creare una "migrazione vuota" utilizzando l'opzione IgnoreChanges:

Add-Migration NameOfMigration -IgnoreChanges

Poi il comando UpdateDatabase avrà successo.


Fonte del problema

L'origine dell'errore che si verifica quando si aggiorna il database è perché i negozi EF un'istantanea del modello di una migrazione si riferisce al file resx all'interno del file di migrazione .

In questo caso gli sviluppatori B un'istantanea del "modello attuale" non è corretto dopo aver/fusione delle modifiche apportate dallo sviluppatore A.

+0

Il video spiega tutto. Questa dovrebbe essere la risposta accettata, a mio parere. –

Problemi correlati