Генерация миграции динамического заполнения данных в EF Core 2.1 в зависимости от bool-флага DbContext с использованием в OnModelCreating () - PullRequest
0 голосов
/ 04 июля 2018

Текущая ситуация

Здравствуйте, у меня есть стандартная библиотека 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.

1 Ответ

0 голосов
/ 04 июля 2018

OnModelCreating - это , а не , запускаемое вызовом конструктора base DbContext, поэтому нет абсолютно никаких проблем с сохранением переданных аргументов для членов класса.

В вашем конкретном сценарии OnModelCreating запускается при обращении к свойству ChangeTracker перед сохранением переданного аргумента:

public MyDbContext(DbContextOptions<MyDbContext> options, bool shouldSeedData = false) :
    base(options)
{
    ChangeTracker.LazyLoadingEnabled = false; // <--

    _shouldSeedData = shouldSeedData;
}

Просто поменяйте строки, и проблема будет решена. И вообще, всегда инициализируйте членов своего класса перед доступом к любому свойству контекста.

...