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.
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:
IDENTITY INSERT ON
(in Sql Server).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.