Seeding dei dati

Il seeding dei dati è il processo di popolamento di un database con un set iniziale di dati.

È possibile eseguire questa operazione in EF Core in diversi modi:

  • Dati di inizializzazione del modello
  • Personalizzazione della migrazione manuale
  • Logica di inizializzazione personalizzata

Dati di inizializzazione del modello

A differenza di EF6, in EF Core i dati di seeding possono essere associati a un tipo di entità come parte della configurazione del modello. Le migrazioni di EF Core possono quindi calcolare automaticamente le operazioni di inserimento, aggiornamento o eliminazione da applicare durante l'aggiornamento del database a una nuova versione del modello.

Nota

Le migrazioni considerano solo le modifiche del modello quando si determina l'operazione da eseguire per ottenere i dati di inizializzazione nello stato desiderato. Di conseguenza, eventuali modifiche apportate ai dati eseguiti all'esterno delle migrazioni potrebbero andare perse o causare un errore.

Ad esempio, in questo modo verranno configurati i dati di inizializzazione per un Blog in OnModelCreating:

modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, Url = "http://sample.com" });

Per aggiungere entità con una relazione, è necessario specificare i valori di chiave esterna:

modelBuilder.Entity<Post>().HasData(
    new Post { BlogId = 1, PostId = 1, Title = "First post", Content = "Test 1" });

Se il tipo di entità ha proprietà nello stato shadow, è possibile usare una classe anonima per fornire i valori:

modelBuilder.Entity<Post>().HasData(
    new { BlogId = 1, PostId = 2, Title = "Second post", Content = "Test 2" });

I tipi di entità di proprietà possono essere sottoposto a seeding in modo simile:

modelBuilder.Entity<Post>().OwnsOne(p => p.AuthorName).HasData(
    new { PostId = 1, First = "Andriy", Last = "Svyryd" },
    new { PostId = 2, First = "Diego", Last = "Vega" });

Per altre informazioni di contesto, vedere il progetto di esempio completo.

Dopo aver aggiunto i dati al modello, è necessario usare le migrazioni per applicare le modifiche.

Suggerimento

Se è necessario applicare le migrazioni come parte di una distribuzione automatizzata, è possibile creare uno script SQL che può essere visualizzato in anteprima prima dell'esecuzione.

In alternativa, è possibile usare context.Database.EnsureCreated() per creare un nuovo database contenente i dati di inizializzazione, ad esempio per un database di test o quando si usa il provider in memoria o qualsiasi database non relazionale. Si noti che se il database esiste già, EnsureCreated() non aggiornerà né lo schema né i dati di inizializzazione nel database. Per i database relazionali non è consigliabile chiamare EnsureCreated() se si prevede di usare le migrazioni.

Limitazioni dei dati di inizializzazione del modello

Questo tipo di dati di inizializzazione viene gestito dalle migrazioni e lo script per aggiornare i dati già presenti nel database deve essere generato senza connettersi al database. Ciò impone alcune restrizioni:

  • Il valore della chiave primaria deve essere specificato anche se in genere viene generato dal database. Verrà usato per rilevare le modifiche ai dati tra le migrazioni.
  • I dati con seeding in precedenza verranno rimossi se la chiave primaria viene modificata in qualsiasi modo.

Questa funzionalità è quindi più utile per i dati statici che non devono cambiare all'esterno delle migrazioni e non dipendono da altri elementi nel database, ad esempio i codici ZIP.

Se lo scenario include una delle opzioni seguenti, è consigliabile usare la logica di inizializzazione personalizzata descritta nell'ultima sezione:

  • Dati temporanei per i test
  • Dati che dipendono dallo stato del database
  • Dati di grandi dimensioni (i dati di seeding vengono acquisiti negli snapshot di migrazione e i dati di grandi dimensioni possono portare rapidamente a file di grandi dimensioni e prestazioni ridotte).
  • Dati che devono essere generati dal database con valori chiave, incluse le entità che usano chiavi alternative come identità
  • Dati che richiedono una trasformazione personalizzata (non gestita dalle conversioni di valori), ad esempio alcuni hash delle password
  • Dati che richiedono chiamate all'API esterna, ad esempio ASP.NET ruoli di identità di base e creazione di utenti

Personalizzazione della migrazione manuale

Quando viene aggiunta una migrazione, le modifiche ai dati specificati con HasData vengono trasformate in chiamate a InsertData(), UpdateData()e DeleteData(). Un modo per aggirare alcune delle limitazioni di HasData consiste nell'aggiungere manualmente queste chiamate o operazioni personalizzate alla migrazione.

migrationBuilder.InsertData(
    table: "Blogs",
    columns: new[] { "Url" },
    values: new object[] { "http://generated.com" });

Logica di inizializzazione personalizzata

Un modo semplice e potente per eseguire il seeding dei dati consiste nell'usare DbContext.SaveChanges() prima che la logica principale dell'applicazione inizi l'esecuzione.

using (var context = new DataSeedingContext())
{
    context.Database.EnsureCreated();

    var testBlog = context.Blogs.FirstOrDefault(b => b.Url == "http://test.com");
    if (testBlog == null)
    {
        context.Blogs.Add(new Blog { Url = "http://test.com" });
    }

    context.SaveChanges();
}

Avviso

Il codice di seeding non deve far parte della normale esecuzione dell'app perché ciò può causare problemi di concorrenza quando sono in esecuzione più istanze e richiederebbe anche all'app l'autorizzazione per modificare lo schema del database.

A seconda dei vincoli della distribuzione, il codice di inizializzazione può essere eseguito in modi diversi:

  • Esecuzione dell'app di inizializzazione in locale
  • Distribuzione dell'app di inizializzazione con l'app principale, richiamando la routine di inizializzazione e disabilitando o rimuovendo l'app di inizializzazione.

Questo può in genere essere automatizzato usando i profili di pubblicazione.