How to properly write seed method in Entity Framework Core?

asp.net c# entity-framework-core

Question

I am learning ASP.NET and I am using EF Core in my project. I would like to seed some test data when I'm running my migrations. Here's my AppDbContext class and my model classes:

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

As you could notice there's a little bit complicated relation between Game and Studio entities. Not only game has a studio which created the game, but also a studio has a list of games developed by them.

If I had to write OnModelCreating method, I would firstly create some test people and then use them to fill Employees field in Studio class. The problem occurs when I have to create games. Studio does not exist yet and if I wanted to create studios first, games are a missing element.

Do I have to manually create some games, ignoring the Studio reference, then instantiate studios, and then manually link object together, or is there any better solution? Best regards.

1
2
12/25/2019 1:42:14 PM

Accepted Answer

Maybe it's because people only read the title, but everybody jumped to how to seed in Entity Framework, period. Next, some came up with AddOrUpdate which doesn't exist in EF core.

Seeding in EF core has changed dramatically compared to EF6. In EF6's Seed method it was possible to save an object graph, i.e. objects containing references and collections. However, the AddOrUpdate method had a couple of issues (I won't spell them out here) that made its behavior very hard to follow or even get right if you weren't aware of them.

As an (over)reaction to that, the EF team decided to no longer let EF determine whether an entity should be added or updated. Seeding should either add an entity if it's non-existing, or do nothing otherwise. Never update. These considerations lead to a greatly (over)simplified seeding mechanism in EF core:

  • Entities should be identified by their hard-coded primary keys . If necessary, they're inserted with IDENTITY INSERT ON (in Sql Server).
  • Foreign keys should also be hard coded.
  • Navigation properties can't be populated. If they are, EF will throw an exception like

    InvalidOperationException: The seed entity for entity type 'Studio' cannot be added because it has the navigation 'Games' set. To seed relationships you need to add the related entity seed to 'Game' and specify the foreign key values {'StudioId'}.

That means that you have to add StudioId to Game. And StudioId and GenreId to Person. Independent associations (a Studio reference without StudioId) aren't supported. (I think this is a far-reaching architectural decision).

Doing that, your seeding code, simplified a bit, could look like:

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

The circular reference Studio ⟷ Game doesn't matter here because it represents only one foreign key.

However, circular references over two foreign keys are impossible. Suppose Studio had a Director property of type Person referred to by DirectorId and the director is also an employee:

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

Now there's a chicken-and-egg problem: both entities can only be inserted if the other one is inserted first.

InvalidOperationException: Unable to save changes because a circular dependency was detected in the data to be saved.

I think that's another far-reaching consequence of this design decision. In EF6, even with its crippled AddOrUpdate, at least it was possible to call SaveChanges twice to accommodate this scenario.

Considering that on top of all this, seeding isn't migration-friendly my stance on data seeding is: either don't support it (I wouldn't mind) or support it well.

2
12/26/2019 8:52:04 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow