如何在 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 中播种?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Entity Framework Core 2 中播种?

如何处置 Entity Framework Core 内存数据库

如何在 Entity Framework Core 中将连接字符串传递给 DBContext?

如何使用 Entity Framework Core 正确保存 DateTime?

如何在 .NET Core 3.0 Entity Framework 中执行组加入?

请问在 .NET Core 中如何让 Entity Framework Core 在日志中记录由 LINQ 生成的SQL语句?