如何在Entity Framework Core中正确编写种子方法?

时间:2019-12-25 13:21:29

标签: c# asp.net entity-framework-core

我正在学习ASP.NET,并且在我的项目中使用EF Core。在运行迁移时,我想播种一些测试数据。这是我的AppDbContext类和模型类:

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<Game> Games { get; set; }
    public DbSet<Studio> Studios { get; set; }
    public DbSet<Person> People { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        var converter = new EnumToNumberConverter<Genre, byte>();

        builder.Entity<Game>().Property(item => item.Genre).HasConversion(converter);
    }
}

public class Person
{
    [Key]
    public int Id { get; set; }
    [Required]
    [StringLength(30)]
    public string Name { get; set; }
    [Required]
    [StringLength(30)]
    public string Surname { get; set; }
}

public class Game
{
    [Key]
    public int Id { get; set; }

    [Required]
    [StringLength(50)]
    public string Name { get; set; }

    [Required]
    [Range(0.0, 1000.0)]
    public double Price { get; set; }

    public Genre Genre { get; set; }

    [Required]
    public Studio Studio { get; set; }
}

public class Studio
{
    [Key]
    public int Id { get; set; }
    [Required]
    [StringLength(50)]
    public string Name { get; set; }

    public IEnumerable<Game> Games { get; set; }
    [Required]
    public decimal Budget { get; set; }
    public IEnumerable<Person> Employees { get; set; }
}

您可能会注意到Game和Studio实体之间存在一些复杂的关系。游戏不仅具有创建游戏的工作室,而且工作室具有由其开发的游戏列表。

如果必须编写OnModelCreating方法,我将首先创建一些测试人员,然后使用它们来填充Employees类中的Studio字段。当我必须创建游戏时,会出现问题。 Studio还不存在,如果我想先创建工作室,那么游戏就是一个缺少的元素。

我是否必须手动创建一些游戏,忽略Studio参考,然后实例化Studio,然后手动将对象链接在一起,或者有更好的解决方案吗?最好的问候。

5 个答案:

答案 0 :(得分:1)

也许是因为人们只读标题,但每个人都跳到如何在Entity Framework中播种。接下来,有人提出了AddOrUpdate,它在EF核心中不存在。

与EF6相比,EF核心中的播种已发生了巨大变化。在EF6的Seed方法中,可以保存对象图,即包含引用和集合的对象。但是,AddOrUpdate方法存在两个问题(在这里我不会将它们拼写出来),使得它的行为很难遵循,甚至在您不了解它们的情况下也可以正确执行。

作为对此的(过度)反应,EF团队决定不再让EF确定是否应添加或更新实体。播种应添加一个不存在的实体,否则不执行任何操作。永不更新。这些考虑导致EF核心中的播种机制大大简化了:

  • 实体应通过其硬编码的主键来标识 。如有必要,它们将与IDENTITY INSERT ON一起插入(在Sql Server中)。
  • 外键也应进行硬编码。
  • 无法填充导航属性。如果是这样,EF将抛出异常,例如
      

    InvalidOperationException:无法添加实体类型“ Studio”的种子实体,因为它已设置了导航“ Games”。要播种关系,您需要将相关实体种子添加到“游戏”并指定外键值{'StudioId'}。

这意味着您必须将StudioId添加到GameStudioIdGenreIdPerson。不支持独立关联(无Studio的{​​{1}}引用)。 (我认为这是一个意义深远的体系结构决定。)

这样做,您的种子代码经过简化,看起来像:

StudioId

循环引用var games = new[] { new Game{ Id = 1, Name = "Game1", StudioId = 1 }, new Game{ Id = 2, Name = "Game2", StudioId = 1 }, }; var studio = new Studio { Id = 1, Name = "Studio1", }; modelBuilder.Entity<Studio>().HasData(studio); modelBuilder.Entity<Game>().HasData(games); Studio在这里无关紧要,因为它仅表示一个外键。

但是,不可能对两个外键进行循环引用。假设Game具有Studio所引用的Director类型的Person属性,并且主管也是雇员:

DirectorId

现在有一个鸡和蛋的问题:两个实体只有在先插入另一个实体后才能插入。

  

InvalidOperationException:无法保存更改,因为在要保存的数据中检测到循环依赖性。

我认为这是此设计决策的另一个深远影响。在EF6中,即使var director = new Person { Id = 1, Name = "Director1", StudioId = 1 }; // Employee of Studio1 var studio = new Studio { Id = 1, Name = "Studio1", DirectorId = 1 // Director is "Director1" }; 瘫痪了,至少也可以两次调用AddOrUpdate来适应这种情况。

考虑到所有这些,播种并不适合迁移,我对数据播种的立场是:要么不支持它(我不介意),要么很好地支持它。

答案 1 :(得分:-1)

replacePersistentStore:…

在PM插入中: 1.启用迁移 2.添加迁移初始 3.更新数据库

答案 2 :(得分:-1)

您应该使用ModelBuilder播种数据:

modelBuilder.Entity<Post>().HasData(
    new Post() { BlogId = 1, PostId = 1, Title = "First post", Content = "Test 1" });

答案 3 :(得分:-1)

如果您不喜欢使用“ ef core modelbuilder”或“ ef core migrations”,则可以在应用程序层中创建默认记录。您不需要模型构建器或迁移工具。像这样。

Startup.cs

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
   if (env.IsDevelopment())
   {
    SeedData.Initialize(app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope().ServiceProvider);

       app.UseDeveloperExceptionPage();
   }

    // your code...
}

SeedData.cs

internal class SeedData
{
    static AppDbContext _dataContext;

    internal static void Initialize(IServiceProvider serviceProvider)
    {
        _dataContext = (AppDbContext)serviceProvider.GetService(typeof(AppDbContext));

        var ensureCreated = _dataContext.Database.EnsureCreated();
        if (ensureCreated)
        {
            CreatePersonData();
            CreateGameData();
            CreateStudioData();

            try
            {
                _dataContext.SaveChanges();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
    }

    private static void CreatePersonData() { /* your code... */ }
    private static void CreateGameData() { /* your code... */ }
    private static void CreateStudioData() { /* your code... */ }
}

答案 4 :(得分:-1)

我将创建一个扩展方法来播种您的数据:

public static class DataContextExtensions
{
    public static void EnsureSeedDataForContext(this AppDbContext context)
    {

    // first, clear the database.  This ensures we can always start fresh. This is optional ! 
        context.Games.RemoveRange(context.Games);
        context.SaveChanges();

        // init seed data
        var games = new List<Games>()
        {
            new Games()
            {
                 Id = new Guid("25320c5e-f58a-4b1f-b63a-8ee07a840bdf"),
                 Name = "Stephen King",
                 Price= 14.99
                 Genre =  new Genre(),
                 Studio = new Studio(),
            }
        }

        //can seed other context data here

        context.Games.AddRange(games);
        context.SaveChanges();
    }
}

然后在configure方法中的startup.cs文件中,可以调用扩展方法,然后执行迁移命令:

 public void Configure(IApplicationBuilder app, IHostingEnvironment env, AppDBContext appDbContext)
 {
      appDbContext.EnsureSeedDataForContext();
      app.UseHttpCacheHeaders();
      app.UseMvc();
 }

对我来说,在存根数据时它并不复杂且不易操作。但是上面的例子也很棒!希望这会有所帮助。