EF Core 2.1 中的动态数据播种迁移生成取决于在 OnModelCreating() 中使用的 DbContext bool-flag

Dynamic data seeding migration-generation in EF Core 2.1 depending on DbContext bool-flag with usage in OnModelCreating()

现状

你好,我有一个 dotnet 标准库,我在其中使用 EF Core 2.1.1(代码优先方法)来访问持久层。为了创建迁移,我使用了一个单独的 dotnet 核心控制台应用程序(在同一解决方案中),其中包含一个 IDesignTimeDbContextFactory<T> 实现。
有必要播种一些数据,我想以一种舒适的方式实现它,因为将来要播种的数据将被扩展或修改。因此,在实现的 IEntityTypeConfiguration 中,我使用扩展方法 .HasData() 获取要播种的对象数组。该数组将从一个单独的 class (TemplateReader) 提供,它从 JSON 文件加载对象(将在其中完成扩展和修改工作)。因此,可以修改 JSON 文件的内容并添加一个包含生成代码的新迁移以插入 (modelBuilder.InsertData())、更新 (modelBuilder.UpdateData()) 或删除 (modelBuilder.DeleteData() ) 语句。
因为我不会发布 JSON 文件,并且我想避免加载序列化数据以进行播种和执行 .HasData(),我想使用一个 bool 值,该值将被赋予 DbContext 由构造函数。
为了避免在不需要为迁移调用种子设定(和 .HasData())时使用 bool 值,我有一个重载的构造函数,它使用默认值 false 实现。 此外,我不会使用 OnConfiguring 因为我想灵活地在我的 IoC 容器中或单独设置 DbContextOptions<T> 对象进行测试。


代码

以下代码包含重命名的变量以更加匿名地描述项目的内容,但代表当前实现的逻辑。

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);
    }
}

模板类型配置:

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;)。那是因为 base constructor 将被立即调用,然后是我的。因此 _shouldSeedData 的值是 false 当它将被赋予 TemplateTypeConfiguration.
因此,如果我修改了上述 JSON 文件,Add-Migration 会导致没有任何逻辑的 "empty" 迁移。


已经测试过的方法

我已经尝试将 IModelCacheKeyFactory 与自己的 ModelCacheKey 对象一起使用,但没有成功。作为模板,我使用了这个 .

我测试的另一种方法是将 _shouldSeedData 设置为 public static 变量并将其从 MyDesignTimeDbContextFactory 设置为 true,但在我看来,这是一个非常肮脏的方法我想避免在生产代码中实施的解决方案。

也应该可以使用 DbContextOptionsBuilder<T>UseModel(IModel) 扩展方法来避免使用 OnModelCreating 并用需要的 shouldSeedData = false 初始化 TemplateTypeConfiguration .这种方法的一个缺点是有重复的代码,这些代码将在 TemplateTypeConfiguration 的构造函数值上有所不同。在我看来,与 public 静态方法一样令人讨厌。


问题

是否有一个干净的解决方案来实现由构造函数设置 _shouldSeedData,使 OnModelCreating 可以在设计时使用正确的值 (true)?
在生产中它应该是 false 并且在 TemplateTypeConfiguration 中提到的 TemplateReader 不应该被调用,因为 if-condition.

OnModelCreating不是由基础DbContext构造函数调用触发的,所以将传递的参数保存到[=23=是绝对没有问题的] 成员。

在您的具体场景中,OnModelCreating 是通过在存储传递的参数之前访问 ChangeTracker 属性 触发的:

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

    _shouldSeedData = shouldSeedData;
}

简单的调换线路,问题就迎刃而解了。通常,在访问任何上下文 属性.

之前始终初始化您的 class 成员