Ciao, ho una libreria standard dotnet in cui utilizzo EF Core 2.1.1 (codice primo approccio) per accedere al livello di persistenza. Per creare migrazioni, utilizzo un'applicazione console core dotnet separata (nella stessa soluzione) che contiene IDesignTimeDbContextFactory<T>
.
È necessario seminare alcuni dati e voglio realizzarli in modo confortevole perché in futuro i dati da seminare saranno estesi o piuttosto modificati. Pertanto, in IEntityTypeConfiguration
implementato, utilizzo il metodo di estensione .HasData()
che ottiene una matrice di oggetti da seminare. L'array verrà fornito da una classe separata ( TemplateReader
), che carica gli oggetti da un file JSON (in cui verrà eseguito il lavoro di estensione e modifica). Quindi è possibile modificare il contenuto del file JSON e aggiungere una nuova migrazione che conterrà il codice generato in Insert ( modelBuilder.InsertData()
), Update ( modelBuilder.UpdateData()
) o Delete ( modelBuilder.DeleteData()
).
Perché non spedirò il file JSON e voglio evitare di caricare i dati serializzati per il seeding e l'esecuzione di .HasData()
, voglio usare un valore bool
che sarà dato in DbContext
dal costruttore.
Per evitare l'utilizzo del valore bool se non è necessario chiamare la seeding per la migrazione (e .HasData()
), ho un costruttore sovraccaricato implementato con il valore predefinito false. Inoltre, non userò OnConfiguring
perché voglio essere flessibile DbContextOptions<T>
oggetto DbContextOptions<T>
nel mio contenitore IoC o separatamente per i test.
Il seguente codice contiene variabili rinominate per essere più anonime sul contenuto del progetto, ma rappresenta la logica implementata corrente.
MyDesignTimeDbContextFactory :
public class MyDesignTimeDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[] args)
{
var connectionString = ConfigurationManager.ConnectionStrings["SqlServer"].ConnectionString;
var contextOptionsBuilder = new DbContextOptionsBuilder<MyDbContext>()
.UseSqlServer(connectionString);
return new MyDbContext(contextOptionsBuilder.Options, true);
}
}
MyDbContext :
public sealed class MyDbContext : DbContext
{
private readonly bool _shouldSeedData;
public DbSet<Content> Contents { get; set; }
public DbSet<Template> Templates { get; set; }
public MyDbContext(DbContextOptions<MyDbContext> options, bool shouldSeedData = false) :
base(options)
{
ChangeTracker.LazyLoadingEnabled = false;
_shouldSeedData = shouldSeedData;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("mySchema");
modelBuilder.ApplyConfiguration(new TemplateTypeConfiguration(_shouldSeedData));
base.OnModelCreating(modelBuilder);
}
}
TemplateTypeConfiguration :
public class TemplateTypeConfiguration : IEntityTypeConfiguration<Template>
{
private readonly bool _shouldSeedData;
public TemplateTypeConfiguration(bool shouldSeedData)
{
_shouldSeedData = shouldSeedData;
}
public void Configure(EntityTypeBuilder<Template> builder)
{
builder.Property(p => p.ModuleKey)
.IsRequired();
builder.Property(p => p.Html)
.IsRequired();
if (_shouldSeedData)
{
// reads all templates from configuration file for seeding
var templateReader = new TemplateReader(Directory.GetCurrentDirectory());
var templates = templateReader.GetTemplates().ToArray();
builder.HasData(templates);
}
}
}
Template ( entità ):
public class Template
{
public int Id { get; set; }
public string ModuleKey { get; set; }
public string Html { get; set; }
public virtual ICollection<Content> Contents { get; set; }
}
Per quanto ne so e ho già testato, OnModelCreating(ModelBuilder)
verrà chiamato prima che il valore bool del costruttore venga impostato nel costruttore ( _shouldSeedData = shouldSeedData;
). Questo perché il costruttore di base verrà chiamato immediatamente, seguito dal mio. Quindi il valore di _shouldSeedData
è false
quando verrà fornito in TemplateTypeConfiguration
.
Per questo Add-Migration
comporta una migrazione "vuota" senza alcuna logica, se ho modificato il file JSON sopra menzionato.
Ho già provato a utilizzare IModelCacheKeyFactory
con un oggetto ModelCacheKey
senza esito positivo. Come modello, ho usato questa domanda-SO .
Un altro approccio che ho provato è stato impostare _shouldSeedData
come variabile public static
e impostarlo da MyDesignTimeDbContextFactory
su true
, ma a mio parere è una soluzione molto sporca che voglio evitare di implementare nel codice di produzione.
Dovrebbe anche essere possibile utilizzare il metodo di estensione UseModel(IModel)
DbContextOptionsBuilder<T>
per evitare l'uso di OnModelCreating
e inizializzare TemplateTypeConfiguration
con shouldSeedData = false
. Uno svantaggio di questo approccio è di avere un codice duplicato che differirà nel valore del costruttore del TemplateTypeConfiguration
. A mio avviso tanto sgradevole quanto l'approccio statico pubblico.
C'è una soluzione pulita da raggiungere per impostare _shouldSeedData
dal costruttore che OnModelCreating
potrebbe usarlo con il valore corretto ( true
) in fase di progettazione?
In produzione dovrebbe essere false
e menzionato TemplateReader
in TemplateTypeConfiguration
non dovrebbe essere chiamato a causa di if-condition.
OnModelCreating
non viene attivato dalla chiamata di base DbContext
costruttore, quindi non c'è assolutamente alcun problema di salvare gli argomenti passati ai membri della classe.
Nello scenario concreto, OnModelCreating
viene attivato dall'accesso alla proprietà ChangeTracker
prima di archiviare l'argomento passato:
public MyDbContext(DbContextOptions<MyDbContext> options, bool shouldSeedData = false) :
base(options)
{
ChangeTracker.LazyLoadingEnabled = false; // <--
_shouldSeedData = shouldSeedData;
}
Basta scambiare le linee e il problema sarà risolto. In generale, inizializza sempre i membri della classe prima di accedere a qualsiasi proprietà del contesto.