如何在 Entity Framework Core 2 中播种?
Posted
技术标签:
【中文标题】如何在 Entity Framework Core 2 中播种?【英文标题】:How to seed in Entity Framework Core 2? 【发布时间】:2017-12-22 05:39:52 【问题描述】:我有两张桌子,我想用种子填充它们。
我在 Ubuntu 中使用 ASP.NET Core 2。
如何填充两个表的数据,其中一个通过外键连接到另一个?
Flowmeter有很多note,note属于Flowmeter。
我想做这样的事情,但它应该存储在数据库中:
new Flowmeter
Make = "Simple model name",
SerialNum = 45,
Model = "Lor Avon",
Notes = new List<Note>()
new Note() Value = 45, CheckedAt = System.DateTime.Now ,
new Note() Value = 98, CheckedAt = System.DateTime.Now
【问题讨论】:
据我所知,.net core 中的最佳解决方案是创建一个控制台项目来为您的数据库播种 非常简单实用的解决方案:EF Core Seed data 【参考方案1】:从 Entity Framework Core 2.1 开始,现在有一种新的数据播种方法。在您的DbContext
类中覆盖OnModelCreating
:
protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<Blog>().HasData(new Blog BlogId = 1, Url = "http://sample.com" );
而对于相关实体,使用匿名类并指定相关实体的外键:
modelBuilder.Entity<Post>().HasData(
new BlogId = 1, PostId = 1, Title = "First post", Content = "Test 1",
new BlogId = 1, PostId = 2, Title = "Second post", Content = "Test 2");
重要提示:请注意,在您的 OnModelCreating 方法和 Update-Database 输入此数据后,您需要运行添加迁移以更新您的数据。
官方文档是updated。
【讨论】:
你会如何做一个条件种子?我们需要在树中为根节点播种,但仅在第一次创建 DB 时,之后再创建,目前我们有一个类和一个方法来检查数据是否存在然后不播种。 @Matt 数据播种现在是一个迁移步骤。只要您正确使用迁移,数据就不会再次被播种!这是我们必须与 EF6 不同的主要原因之一。 @MartínColl 试图找到更新的文档,但大多数使用旧的解决方案,任何指向新文档的指针更详细地描述了这一点? 您应该查看HasData
方法:docs.microsoft.com/en-us/ef/core/modeling/data-seeding
请在您的 OnModelCreating
方法和 Update-Database 中输入此数据以更新您的数据后,将其放在您需要运行 ADD-MIGRATION 的评论中!!【参考方案2】:
这是我的 EF Core 2.0 解决方案,改编自 https://docs.microsoft.com/en-us/aspnet/core/migration/1x-to-2x/#move-database-initialization-code
在程序.cs中
public class Program
public static void Main(string[] args)
BuildWebHost(args).Seed().Run();
....
然后是我的播种机类
public static class DatabaseSeedInitializer
public static IWebHost Seed(this IWebHost host)
using (var scope = host.Services.CreateScope())
var serviceProvider = scope.ServiceProvider;
try
Task.Run(async () =>
var dataseed = new DataInitializer();
await dataseed.InitializeDataAsync(serviceProvider);
).Wait();
catch (Exception ex)
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
return host;
【讨论】:
我真的很喜欢您如何设法清理代码并使其感觉更加集成。我不喜欢通常存在的标准锅炉点代码如何将其全部转储到程序 Main 中。 :) 记录后重新抛出错误是否有意义? 我们可以看看你的 DataInitializer 类吗?谢谢! 公共类 DataInitializer 私有 ApplicationDbContext _context; //... //... //... public async Task InitializeDataAsync(IServiceProvider serviceProvider) _context = serviceProvider.GetService(); //... //... //种子数据 【参考方案3】:tl;dr:看看我的dwCheckApi project,看看我是如何实现它的。
正如其他人所说,您可以从 JSON 或类似文件中读取您的种子数据(如果您愿意,它可以进行源代码控制)。
我在项目中实现它的方式是在 Startup 类的 Configure
方法中调用一个方法(仅在开发时):
if (env.IsDevelopment())
app.EnsureDatabaseIsSeeded(false);
调用如下:
public static int EnsureDatabaseIsSeeded(this IApplicationBuilder applicationBuilder,
bool autoMigrateDatabase)
// seed the database using an extension method
using (var serviceScope = applicationBuilder.ApplicationServices
.GetRequiredService<IServiceScopeFactory>().CreateScope())
var context = serviceScope.ServiceProvider.GetService<DwContext>();
if (autoMigrateDatabase)
context.Database.Migrate();
return context.EnsureSeedData();
我的 DbContext 是 DwContext
类型,这是一个扩展 EF Core DbContext
类型的类
EnsureSeedData
扩展方法如下所示:
public static int EnsureSeedData(this DwContext context)
var bookCount = default(int);
var characterCount = default(int);
var bookSeriesCount = default(int);
// Because each of the following seed method needs to do a save
// (the data they're importing is relational), we need to call
// SaveAsync within each method.
// So let's keep tabs on the counts as they come back
var dbSeeder = new DatabaseSeeder(context);
if (!context.Books.Any())
var pathToSeedData = Path.Combine(Directory.GetCurrentDirectory(), "SeedData", "BookSeedData.json");
bookCount = dbSeeder.SeedBookEntitiesFromJson(pathToSeedData).Result;
if (!context.BookCharacters.Any())
characterCount = dbSeeder.SeedBookCharacterEntriesFromJson().Result;
if (!context.BookSeries.Any())
bookSeriesCount = dbSeeder.SeedBookSeriesEntriesFromJson().Result;
return bookCount + characterCount + bookSeriesCount;
此应用程序旨在显示书籍、角色和系列之间的关系。这就是为什么有三个播种机的原因。
其中一种播种器方法如下所示:
public async Task<int> SeedBookEntitiesFromJson(string filePath)
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException($"Value of filePath must be supplied to nameof(SeedBookEntitiesFromJson)");
if (!File.Exists(filePath))
throw new ArgumentException($"The file filePath does not exist");
var dataSet = File.ReadAllText(filePath);
var seedData = JsonConvert.DeserializeObject<List<Book>>(dataSet);
// ensure that we only get the distinct books (based on their name)
var distinctSeedData = seedData.GroupBy(b => b.BookName).Select(b => b.First());
_context.Books.AddRange(distinctSeedData);
return await _context.SaveChangesAsync();
这里可能有一些代码不是很好,但它可能是你反弹的起点。
因为播种器仅在开发环境中被调用,所以您需要确保您的应用程序以这种方式启动(如果从命令行启动,您可以使用ASPNETCORE_ENVIRONMENT=Development dotnet run
来确保它在开发环境中启动)。
这也意味着您需要一种不同的方法来为生产环境中的数据库播种。在 dwCheckApi 中,我有一个控制器,可以调用它来为数据库播种(查看 DatabaseController's SeedData method 以了解我是如何做到的)。
【讨论】:
【参考方案4】:我不喜欢 Microsoft 文档中所写的 HasData 方法,因为我无法以这种方式保持我的迁移干净并且因为我的 DbContext
中的 OnModelCreating()
开始依赖于感觉有点错误并导致问题的数据带有随机数据生成器。
对我来说,最有效和最舒适的方法是为我的每个 DbSet 创建一个种子类,看起来像这样。 (使用 Bogus 库就像呼吸一样容易)
using Bogus;
// namespace, class, etc.
// CategorySeeder seed method
public int Seed(AppDbContext context)
var faker = new Faker<Category>()
.RuleFor(r => r.IsGroup, () => true)
.RuleFor(r => r.Parent, () => null)
.RuleFor(r => r.UniversalTimeTicks, () => DateTime.Now.ToUniversalTime().Ticks)
.RuleFor(r => r.Title, f => "Folder: " + f.Random.Word());
var folders1 = faker.Generate(5);
faker.RuleFor(r => r.Parent, () => folders1.OrderBy(r => Guid.NewGuid()).First());
var folders2 = faker.Generate(10);
var folders3 = folders1.Concat(folders2).ToArray();
faker.RuleFor(r => r.Parent, () => folders3.OrderBy(r => Guid.NewGuid()).First());
faker.RuleFor(r => r.Title, f => f.Random.Word());
faker.RuleFor(r => r.IsGroup, () => false);
var elements = faker.Generate(20);
var allSeeds = elements.Concat(folders3).ToArray();
context.AddRange(allSeeds);
context.SaveChanges();
return allSeeds.Length;
// ProductSeeder Seed method
public int Seed(AppDbContext context)
var faker = new Faker<Product>()
.RuleFor(r => r.Sku, f => f.Random.AlphaNumeric(8))
.RuleFor(r => r.Title, f => f.Random.Word())
.RuleFor(r => r.Category, () => context.Categories.Where(c => !c.IsGroup).OrderBy(o => Guid.NewGuid()).First());
var prod = faker.Generate(50);
context.AddRange(prod);
context.SaveChanges();
return prod.Count;
然后创建服务控制器,它只在开发环境中工作。
public class DataGeneratorController : BaseController
public DataGeneratorController(IServiceProvider sp) : base(sp)
public IActionResult SeedData()
var lst = new List<string>();
if (!_dbContext.Categories.Any())
var count = new CategoryConfiguration().Seed(_dbContext);
lst.Add($"count Categories have been seeded.");
if (!_dbContext.Products.Any())
var count = new ProductConfiguration().Seed(_dbContext);
lst.Add($"count Products have been seeded.");
if (lst.Count == 0)
lst.Add("Nothing has been seeded.");
return Json(lst);
我想随时从 Insomnia\Postman 调用它。
【讨论】:
【参考方案5】:创建种子数据静态类,如
public static class SeedData
public static void Initialize(IServiceProvider serviceProvider)
var context = serviceProvider.GetRequiredService<YourDbContext>();
context.Database.EnsureCreated();
if (!context.Items.Any())
context.Items.Add(entity: new Item() Name = "Green Thunder" );
context.Items.Add(entity: new Item() Name = "Berry Pomegranate" );
context.Items.Add(entity: new Item() Name = "Betty Crocker" );
context.Items.Add(entity: new Item() Name = "Pizza Crust Mix" );
context.SaveChanges();
if (!context.Shoppings.Any())
context.Shoppings.Add(entity:new Shopping() Name="Defualt" );
更新您的 program.cs 代码以插入您的种子数据,如下所示
public class Program
public static void Main(string[] args)
//CreateWebHostBuilder(args).Build().Run();
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
var services = scope.ServiceProvider;
try
var context = services.GetRequiredService<YourDbContext>();
context.Database.Migrate(); // apply all migrations
SeedData.Initialize(services); // Insert default data
catch (Exception ex)
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred seeding the DB.");
host.Run();
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
【讨论】:
这对我来说似乎是播种数据最合理的方式。【参考方案6】:如果有人仍然对这个主题感兴趣,我们创建了一组工具(一个 .net 核心全局工具和一个库)来简化数据播种的过程。
长话短说:您可以将当前数据库的内容保存到一些 JSON 或 XML 文件中,然后将几行代码添加到您的应用程序中,以加载这些文件并将保存在那里的数据导入您的数据库。 该工具集完全免费,open-source.
详细的分步说明是published here。
【讨论】:
【参考方案7】:我在 json 中创建了我的种子,然后将它们批量添加到我的 Asp.net 核心启动中
非常类似于https://garywoodfine.com/how-to-seed-your-ef-core-database/
尚未找到开箱即用的解决方案。
【讨论】:
【参考方案8】:我遇到了同样的问题,我通过以下方式修复了播种:
首先,我将garywoodfine 中的public static bool AllMigrationsApplied(this DbContext context)
添加到我的模型中。
然后我实现了一个服务范围来为 db 播种 -> 参见 this blog
然后我按照this blog 上的教程制作了一个public static void EnsureSeedData
代码以使用NBuilder 和Faker 生成测试数据
我希望这将帮助人们为他们的项目实施自动化测试种子。目前我正忙着自己实现这个,有时间我会发布一些代码示例来说明如何做到这一点。
【讨论】:
【参考方案9】:我正在使用带有“内存数据库”上下文的 Entity Framework 3,并且能够通过执行以下操作来播种数据。
-
在我的
DbContext
类中覆盖OnModelCreating
。例如:
public class NoteContext : DbContext
public DbSet<Note> Notes get; set;
public NoteContext(DbContextOptions<NoteContext> options)
: base(options)
/// <summary>
/// Seed data
/// </summary>
/// <param name="modelBuilder"></param>
protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<Note>().HasData(new[]
new Note NoteId = Guid.NewGuid(), User = "UserA", Message = "Message from UserA" ,
new Note NoteId = Guid.NewGuid(), User = "UserB", Message = "Message from UserB"
);
-
在使用上下文之前调用
context.Database.EnsureCreated()
。例如:
[Route("api/[controller]")]
[ApiController]
public class NotesController : ControllerBase
private readonly NoteContext _context;
public NotesController(NoteContext context)
_context = context;
// Seed data
_context.Database.EnsureCreated();
...
【讨论】:
以上是关于如何在 Entity Framework Core 2 中播种?的主要内容,如果未能解决你的问题,请参考以下文章