[英]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()
获取要播种的对象数组。 该数组将从一个单独的类( TemplateReader
)提供,该类从JSON文件加载对象(其中将完成扩展和修改工作)。 因此,可以修改JSON文件的内容,并添加包含生成的代码的新迁移到Insert( modelBuilder.InsertData()
),Update( modelBuilder.UpdateData()
)或Delete( 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);
}
}
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
的值被赋予TemplateTypeConfiguration
时,它的值为false
。
因此,如果我修改了上述JSON文件,则Add-Migration
导致“空”迁移而没有任何逻辑。
我已经尝试将IModelCacheKeyFactory
与自己的ModelCacheKey
对象一起使用,但没有成功。 作为模板,我使用了这个SO问题 。
我测试的另一种方法是将_shouldSeedData
设置为public static
变量并将其从MyDesignTimeDbContextFactory
设置为true
,但在我看来,这是一个非常脏的解决方案,我想避免在生产代码中实现。
也应该可以使用DbContextOptionsBuilder<T>
的UseModel(IModel)
扩展方法来避免使用OnModelCreating
并使用所需的shouldSeedData = false
初始化TemplateTypeConfiguration
。 这种方法的缺点是具有复制的代码,这些代码在TemplateTypeConfiguration
的构造函数值方面会有所不同。 在我看来,作为公共静态方法令人讨厌。
是否有一个干净的解决方案来实现通过构造函数设置_shouldSeedData
, OnModelCreating
可以在设计时使用正确的值( true
)?
在生产中它应该是false
并且由于if-condition,不应该调用TemplateReader
中的TemplateTypeConfiguration
。
OnModelCreating
不是由基本的DbContext
构造函数调用触发的,因此绝对没有将传递的参数保存到类成员的问题。
在您的具体方案中, OnModelCreating
由存储传递的参数之前访问ChangeTracker
属性触发:
public MyDbContext(DbContextOptions<MyDbContext> options, bool shouldSeedData = false) :
base(options)
{
ChangeTracker.LazyLoadingEnabled = false; // <--
_shouldSeedData = shouldSeedData;
}
只需更换线路,问题就解决了。 通常,在访问任何上下文属性之前始终初始化类成员。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.