Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
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:
-
Seeding dei dati delle opzioni di configurazione (
UseSeeding) - Logica di inizializzazione personalizzata
-
Dati gestiti dal modello (
HasData) - Personalizzazione della migrazione manuale
Opzioni UseSeeding e UseAsyncSeeding metodi di configurazione
EF 9 ha introdotto i metodi UseSeeding e UseAsyncSeeding, che offrono un modo pratico per popolare il database con i dati iniziali. Questi metodi mirano a migliorare l'esperienza di utilizzo della logica di inizializzazione personalizzata (spiegata di seguito). Forniscono una posizione chiara in cui è possibile inserire tutto il codice di seeding dei dati. Inoltre, il codice all'interno dei metodi UseSeeding e UseAsyncSeeding è protetto dal meccanismo di blocco della migrazione per evitare problemi di concorrenza.
I nuovi metodi di seeding vengono chiamati come parte dell'operazione EnsureCreatedMigrate e dotnet ef database update comando, anche se non sono state apportate modifiche al modello e non sono state applicate migrazioni.
Suggerimento
L'uso di UseSeeding e UseAsyncSeeding è il modo consigliato per eseguire il seeding del database con i dati iniziali quando si usa EF Core.
Questi metodi possono essere configurati nel passaggio di configurazione delle opzioni. Ecco un esempio:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFDataSeeding;Trusted_Connection=True;ConnectRetryCount=0")
.UseSeeding((context, _) =>
{
var testBlog = context.Set<Blog>().FirstOrDefault(b => b.Url == "http://test.com");
if (testBlog == null)
{
context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
context.SaveChanges();
}
})
.UseAsyncSeeding(async (context, _, cancellationToken) =>
{
var testBlog = await context.Set<Blog>().FirstOrDefaultAsync(b => b.Url == "http://test.com", cancellationToken);
if (testBlog == null)
{
context.Set<Blog>().Add(new Blog { Url = "http://test.com" });
await context.SaveChangesAsync(cancellationToken);
}
});
Nota
UseSeeding
/
UseAsyncSeedingvengono richiamati durante EnsureCreated/EnsureCreatedAsync e dopo l'applicazione delle migrazioni (ad esempio,/MigrateMigrateAsync , dotnet ef database updatee bundle di migrazione).
Gli strumenti e i bundle di EF Core attualmente si basano sul delegato sincrono, quindi implementare UseSeeding sempre anche se l'applicazione usa normalmente API asincrone.
Comportamento della distribuzione
UseSeeding e UseAsyncSeeding vengono eseguiti solo quando EF Core esegue un'operazione di inizializzazione o migrazione del database. Scegliere un meccanismo di distribuzione di conseguenza:
| Operation | Delegato di seeding richiamato |
|---|---|
EnsureCreated o Migrate |
UseSeeding |
EnsureCreatedAsync o MigrateAsync |
UseAsyncSeeding |
dotnet ef database update o Update-Database |
UseSeeding |
| Bundle di migrazione | UseSeeding |
| Script SQL eseguito da uno strumento SQL esterno | None |
Per la distribuzione automatizzata che deve eseguire UseSeeding, usare un bundle di migrazione o un processo di inizializzazione dedicato. Gli strumenti e i bundle di EF Core richiamano il delegato sincrono, quindi implementano UseSeeding sempre anche se l'applicazione usa normalmente API asincrone. Usare uno script SQL quando è necessaria l'esecuzione di revisione o DBA e i dati di inizializzazione sono rappresentati dalle operazioni di migrazione. Vedere Applicazione delle migrazioni per i compromessi .
Il seeding viene eseguito anche dopo un downgrade della migrazione. Se l'applicazione supporta il downgrade a una migrazione che non contiene tutte le tabelle usate dal codice di seeding, verificare che lo schema richiesto esista prima di eseguirne una query. Ciò è particolarmente importante quando si ripristinano tutte le migrazioni tramite la destinazione 0.
Le applicazioni aspirabili possono coordinare l'esecuzione della migrazione locale e pubblicare bundle o script di migrazione con l'integrazione delle migrazioni di Ef Core aspirare.
Logica di inizializzazione personalizzata
Un modo semplice e potente per eseguire il seeding dei dati consiste nell'usare SaveChangesAsync prima che la logica principale dell'applicazione inizi l'esecuzione. È consigliabile usare UseSeeding e UseAsyncSeeding a tale scopo, tuttavia a volte l'uso di questi metodi non è una soluzione valida. Uno scenario di esempio è quando il seeding richiede l'uso di due contesti diversi in una transazione. Di seguito è riportato un esempio di codice che esegue direttamente l'inizializzazione personalizzata nell'applicazione:
using (var context = new DataSeedingContext())
{
await context.Database.EnsureCreatedAsync();
var testBlog = await context.Blogs.FirstOrDefaultAsync(b => b.Url == "http://test.com");
if (testBlog == null)
{
context.Blogs.Add(new Blog { Url = "http://test.com" });
await context.SaveChangesAsync();
}
}
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.
Dati gestiti del modello
I dati possono anche 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.
Avviso
Le migrazioni considerano solo le modifiche del modello quando si determina quale operazione deve essere eseguita per ottenere i dati gestiti nello stato desiderato. Di conseguenza, eventuali modifiche apportate ai dati eseguiti all'esterno delle migrazioni potrebbero andare perse o causare un errore.
Ad esempio, verranno configurati i dati gestiti per un Country in OnModelCreating:
modelBuilder.Entity<Country>(b =>
{
b.Property(x => x.Name).IsRequired();
b.HasData(
new Country { CountryId = 1, Name = "USA" },
new Country { CountryId = 2, Name = "Canada" },
new Country { CountryId = 3, Name = "Mexico" });
});
Per aggiungere entità con una relazione, è necessario specificare i valori di chiave esterna:
modelBuilder.Entity<City>().HasData(
new City { Id = 1, Name = "Seattle", LocatedInId = 1 },
new City { Id = 2, Name = "Vancouver", LocatedInId = 2 },
new City { Id = 3, Name = "Mexico City", LocatedInId = 3 },
new City { Id = 4, Name = "Puebla", LocatedInId = 3 });
Quando si gestiscono i dati per spostamenti molti-a-molti, l'entità join deve essere configurata in modo esplicito. Se il tipo di entità ha proprietà nello stato shadow ,ad esempio l'entità LanguageCountry join seguente, è possibile usare una classe anonima per fornire i valori:
modelBuilder.Entity<Language>(b =>
{
b.HasData(
new Language { Id = 1, Name = "English" },
new Language { Id = 2, Name = "French" },
new Language { Id = 3, Name = "Spanish" });
b.HasMany(x => x.UsedIn)
.WithMany(x => x.OfficialLanguages)
.UsingEntity(
"LanguageCountry",
r => r.HasOne(typeof(Country)).WithMany().HasForeignKey("CountryId").HasPrincipalKey(nameof(Country.CountryId)),
l => l.HasOne(typeof(Language)).WithMany().HasForeignKey("LanguageId").HasPrincipalKey(nameof(Language.Id)),
je =>
{
je.HasKey("LanguageId", "CountryId");
je.HasData(
new { LanguageId = 1, CountryId = 2 },
new { LanguageId = 2, CountryId = 2 },
new { LanguageId = 3, CountryId = 3 });
});
});
I tipi di entità di proprietà possono essere configurati in modo simile:
modelBuilder.Entity<Language>().OwnsOne(p => p.Details).HasData(
new { LanguageId = 1, Phonetic = false, Tonal = false, PhonemesCount = 44 },
new { LanguageId = 2, Phonetic = false, Tonal = false, PhonemesCount = 36 },
new { LanguageId = 3, Phonetic = true, Tonal = false, PhonemesCount = 24 });
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.
HasData le modifiche vengono convertite in InsertDataoperazioni , UpdateDatae DeleteData quando viene eseguito lo scaffolding di una migrazione. La chiamata Migrate non controlla in modo indipendente la configurazione corrente HasData . Dopo aver modificato i dati gestiti dal modello, aggiungere e distribuire una nuova migrazione.
Suggerimento
Per la distribuzione automatizzata, usare un bundle di migrazione. Usare uno script SQL quando deve essere visualizzato in anteprima o modificato prima dell'esecuzione.
In alternativa, è possibile usare EnsureCreatedAsync per creare un nuovo database contenente i dati gestiti, 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à, EnsureCreatedAsync non aggiornerà né lo schema né i dati gestiti nel database. Per i database relazionali non è consigliabile chiamare EnsureCreatedAsync se si prevede di usare le migrazioni.
Nota
Il popolamento del database tramite il metodo HasData era chiamato "inizializzazione dei dati". Questa denominazione imposta aspettative non corrette, poiché la funzionalità presenta una serie di limitazioni ed è appropriata solo per tipi specifici di dati. Ecco perché abbiamo deciso di rinominarlo in "dati gestiti dal modello". UseSeeding e UseAsyncSeeding devono essere usati per l'inizializzazione dei dati per usi generici.
Limitazioni dei dati gestiti dal modello
Questo tipo di dati 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 inseriti 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 UseSeeding i metodi e UseAsyncSeeding descritti nella prima 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 Core ruoli di identità e creazione di utenti
- Dati non fissi e deterministici, ad esempio il seeding in
DateTime.Now.
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: "Countries",
columns: new[] { "CountryId", "Name" },
values: new object[,]
{
{ 1, "USA" },
{ 2, "Canada" },
{ 3, "Mexico" }
});
migrationBuilder.InsertData(
table: "Languages",
columns: new[] { "Id", "Name", "Details_PhonemesCount", "Details_Phonetic", "Details_Tonal" },
values: new object[,]
{
{ 1, "English", 44, false, false },
{ 2, "French", 36, false, false },
{ 3, "Spanish", 24, true, false }
});
migrationBuilder.InsertData(
table: "Cites",
columns: new[] { "Id", "LocatedInId", "Name" },
values: new object[,]
{
{ 1, 1, "Seattle" },
{ 2, 2, "Vancouver" },
{ 3, 3, "Mexico City" },
{ 4, 3, "Puebla" }
});
migrationBuilder.InsertData(
table: "LanguageCountry",
columns: new[] { "CountryId", "LanguageId" },
values: new object[,]
{
{ 2, 1 },
{ 2, 2 },
{ 3, 3 }
});
Queste operazioni sono appropriate quando i valori e le chiavi vengono corretti quando viene scritta la migrazione. Non eseguono query sullo stato corrente del database. Vedere Operazioni dei dati nelle migrazioni per esempi di trasformazioni SQL specifiche del InsertDataprovider , UpdateDataDeleteData, e .