Текущая ситуация
Здравствуйте, у меня есть стандартная библиотека dotnet, в которой я использую EF Core 2.1.1 (первый подход кода) для доступа к уровню персистентности. Для создания миграций я использую отдельное консольное приложение ядра dotnet (в том же решении), которое содержит реализацию IDesignTimeDbContextFactory<T>
.
Необходимо заполнить некоторые данные, и я хочу реализовать их удобным способом, потому что в будущем данные для заполнения будут расширены или скорее изменены. Поэтому в реализованном IEntityTypeConfiguration
я использую метод расширения .HasData()
, который получает массив объектов для заполнения. Массив будет предоставлен из отдельного класса (TemplateReader
), который загружает объекты из файла JSON (в котором будет выполняться работа по расширению и модификации). Следовательно, можно изменить содержимое файла JSON и добавить новую миграцию, которая будет содержать сгенерированный код для операторов вставки (modelBuilder.InsertData()
), обновления (modelBuilder.UpdateData()
) или удаления (modelBuilder.DeleteData()
).
Поскольку я не буду отправлять файл JSON и хочу избежать загрузки сериализованных данных для заполнения и выполнения .HasData()
, я хочу использовать значение bool
, которое будет передано конструктору в DbContext
.
Чтобы избежать использования значения bool, если нет необходимости вызывать заполнение для миграции (и .HasData()
), у меня есть перегруженный конструктор, реализованный со значением по умолчанию false.
Кроме того, я не буду использовать OnConfiguring
, потому что хочу быть гибким в настройке объекта DbContextOptions<T>
в моем контейнере IoC или отдельно для тестов.
код
Следующий код содержит переименованные переменные для большей анонимности содержимого проекта, но представляет текущую реализованную логику.
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);
}
}
}
Шаблон ( сущность ):
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; }
}
Проблема
Насколько я знаю и я уже тестировал, OnModelCreating(ModelBuilder)
будет вызываться до того, как в конструкторе будет установлено значение bool конструктора (_shouldSeedData = shouldSeedData;
). Это потому, что базовый конструктор будет вызван немедленно, а затем мой. Следовательно, значение _shouldSeedData
равно false
, когда оно будет передано в TemplateTypeConfiguration
.
Из-за этого Add-Migration
приводит к «пустой» миграции без какой-либо логики, если я изменил вышеупомянутый файл JSON.
Уже проверенные подходы
Я уже пытался использовать IModelCacheKeyFactory
с собственным ModelCacheKey
объектом, но безуспешно. В качестве шаблона я использовал этот SO-вопрос .
Еще один подход, который я протестировал, состоял в том, чтобы установить _shouldSeedData
в качестве переменной public static
и установить ее от MyDesignTimeDbContextFactory
до true
, но, на мой взгляд, это очень грязное решение, которое я хочу избежать при внедрении в производство код.
Также следует использовать метод расширения DbContextOptionsBuilder<T>
UseModel(IModel)
, чтобы избежать использования OnModelCreating
и инициализации TemplateTypeConfiguration
необходимым shouldSeedData = false
. Недостатком этого подхода является наличие дублированного кода, который будет отличаться значением конструктора TemplateTypeConfiguration
. На мой взгляд, такой же противный, как публичный статический подход.
Вопрос
Существует ли чистое решение для установки _shouldSeedData
с помощью конструктора, чтобы OnModelCreating
мог использовать его с правильным значением (true
) во время разработки?
В производстве это должно быть false
и упоминание TemplateReader
в TemplateTypeConfiguration
не должно вызываться из-за условия if.