Current situation
Hello, I have a dotnet standard library in which I use EF Core 2.1.1 (code first approach) to access the persistence layer. To create migrations I use a separate dotnet core console application (in the same solution) that contains an IDesignTimeDbContextFactory<T>
implementation.
It is necessary to seed some data and I want to realize it in a comfortable way because in future the data to seed will be extended or rather modified. Therefore in implemented IEntityTypeConfiguration
I use the extension method .HasData()
which gets an array of objects to seed. The array will be provided from a separate class (TemplateReader
), which loads the objects from a JSON file (in which extending and modifying work will be done). Hence it is possible to modify the JSON file's content and add a new migration that will contain generated code to Insert (modelBuilder.InsertData()
), Update (modelBuilder.UpdateData()
) or Delete (modelBuilder.DeleteData()
) statements.
Because I will not ship the JSON file and I want to avoid loading the serialized data for seeding and the execution of .HasData()
, I want to use a bool
value that will be given into DbContext
by the constructor.
To avoid bool value usage if it is not necessary to call the seeding for migration (and .HasData()
) I have an overloaded constructor implemented with default value false.
Furthermore, I will not use OnConfiguring
because I want to be flexible to set up DbContextOptions<T>
object in my IoC container or separately for tests.
Code
The following code contains renamed variables to be more anonym about project's content, but represents current implemented logic.
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 (entity):
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; }
}
Problem
As far as I know and I already tested the OnModelCreating(ModelBuilder)
will be called before constructor's bool value will be set in the constructor (_shouldSeedData = shouldSeedData;
). That's because the base constructor will be called immediately, followed by mine. Hence _shouldSeedData
's value is false
when it will be given into TemplateTypeConfiguration
.
Because of that, an Add-Migration
results in an "empty" migration without any logic, if I have any modified the above-mentioned JSON file.
Already tested approaches
I already tried to use IModelCacheKeyFactory
with an own ModelCacheKey
object without any success. As the template, I used this SO-question.
Another approach I tested was to set up _shouldSeedData
as public static
variable and setting it from MyDesignTimeDbContextFactory
to true
, but in my opinion, it's a very dirty solution that I want to avoid to implement in production code.
It should be also possible to use DbContextOptionsBuilder<T>
's UseModel(IModel)
extension method to avoid the usage of OnModelCreating
and initializing TemplateTypeConfiguration
with needed shouldSeedData = false
. A disadvantage of this approach is to have duplicated code that will differ in TemplateTypeConfiguration
's constructor value. In my opinion as nasty as the public static approach.
Question
Is there a clean solution to achieve to set _shouldSeedData
by the constructor that OnModelCreating
could use it with correct value (true
) on design time?
In production it should be false
and mentioned TemplateReader
in TemplateTypeConfiguration
should not be called because of if-condition.