Data Seeding with EF Core

One of the new EF Core 2.1 features is seeding of data. Contrary to the older version of Entity Framework, seeding with EF Core works together with migrations. This article shows how data seeding can be used with EF Core 2.1.

Farmer with tractor seeding

Seeding Data in OnModelCreated

With the first sample, I’m defining a simple Book type. Predefined books should be written to the database as the database is created. This can easily be achieved by using the HasData method. This method is defined with the EntityTypeBuilder that is returned from the Entity method of the ModelBuilder. The HasData method is defined with the params keyword to allow any number of entity types passed. With the sample code, three Book objects are created and passed to the HasData method.

public class BooksContext : DbContext
{
private const string ConnectionString = @"server=(localdb)\MSSQLLocalDb;database=Books1;trusted_connection=true";
public DbSet<Book> Books { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(ConnectionString)
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Book>().Property(b => b.Title)
.IsRequired()
.HasMaxLength(50);
modelBuilder.Entity<Book>().Property(b => b.Publisher)
.IsRequired(false)
.HasMaxLength(30);
modelBuilder.Entity<Book>().HasData(
new Book { BookId = 1, Title = "Professional C# 6", Publisher = "Wrox Press"},
new Book { BookId = 2, Title = "Professional C# 7", Publisher = "Wrox Press"},
new Book { BookId = 3, Title = "Professional C# 8", Publisher = "Wrox Press"}
);
}
}

view raw
BooksContext.cs
hosted with ❤ by GitHub

The values for the primary key values are required when seeding the data. This differs from the previous Entity Framework implementation, and is used to uniquely identify the records on migration scenarios.

The database can now created using the EnsureCreated method of the DatabaseFacade that is returned from the Database property of the BooksContext:

using (var context = new BooksContext())
{
bool created = context.Database.EnsureCreated();
Console.WriteLine($"Books database created and seeded: {created}");
}

view raw
Program.cs
hosted with ❤ by GitHub

Shadow state with Seeds

One of the great features of EF Core is shadow state. With shadow state, state defined in the table of the database is not mapped with the model type. This state can be retrieved and written using the context.
Defining state just with the context, also requires to pass this data when seeding the records. An overload of the HasData method allows passing an object array. Here, you can pass objects of anonymous types and include all the shadow state with properties.

The MenuItem class defines the properties MenuId, Text, and MenuDate:

public class MenuItem
{
public int MenuItemId { get; set; }
public string Text { get; set; }
[DataType(DataType.Date)]
public DateTime MenuDate { get; set; }
}

view raw
MenuItem.cs
hosted with ❤ by GitHub

In addition to that, the LastUpdated column is used in the MenuItems table. This is defined as shadow state. With the HasData method, anonymous objects are passed. This way it is possible to define any properties – including the shadow state value for LastUpdated:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// shadow state
modelBuilder.Entity<MenuItem>().Property<DateTime>(LastUpdated);
modelBuilder.Entity<MenuItem>().Property(m => m.Text).IsRequired().HasMaxLength(40);
// use anonymous types to pass shadow state information
modelBuilder.Entity<MenuItem>().HasData(
new { MenuItemId = 1, Text = "Wiener Schnitzel mit Kartoffelsalat", MenuDate = new DateTime(2018, 8, 27), LastUpdated = DateTime.Now },
new { MenuItemId = 2, Text = "Faschierter Braten mit Karoffelpüree", MenuDate = new DateTime(2018, 8, 28), LastUpdated = DateTime.Now });
}

view raw
MenusContext.cs
hosted with ❤ by GitHub

With the MenuItem items, the database is created in the same way as the previously created Book objects.

Migrations and Seeding Data

What about migrations and data seeding? This time, the Racer type with the F1Context is created. Racers are seeded with the OnModelCreating method.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Racer>().Property(r => r.Name)
.IsRequired()
.HasMaxLength(100);
modelBuilder.Entity<Racer>().Property(r => r.Team)
.IsRequired(false)
.HasMaxLength(60);
modelBuilder.Entity<Racer>().HasData(
new Racer { Id = 1, Name = "Lewis Hamilton", Team = "Mercedes" },
new Racer { Id = 2, Name = "Sebastian Vettel", Team = "Ferrari" },
new Racer { Id = 3, Name = "Kimi Räikkönen", Team = "Ferrari" },
new Racer { Id = 4, Name = "Valtteri Bottas", Team = "Mercedes" },
new Racer { Id = 5, Name = "Max Verstappen", Team = "Red Bull Racing"},
new Racer { Id = 6, Name = "Daniel Ricciardo", Team = "Red Bull Racing"}
);
}

view raw
F1Context.cs
hosted with ❤ by GitHub

First migrations are added to the project. This is done using the dotnet CLI:

dotnet ef migrations add InitF1 --context F1Context

Adding migrations, adds migration and snapshot classes. The first migration class, InitF1, defines to create the table Racers including racer values taken from the HasData method:

protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Racers",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(maxLength: 100, nullable: false),
Team = table.Column<string>(maxLength: 60, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Racers", x => x.Id);
});
migrationBuilder.InsertData(
table: "Racers",
columns: new[] { "Id", "Name", "Team" },
values: new object[,]
{
{ 1, "Lewis Hamilton", "Mercedes" },
{ 2, "Sebastian Vettel", "Ferrari" },
{ 3, "Kimi Räikkönen", "Ferrari" },
{ 4, "Valtteri Bottas", "Mercedes" },
{ 5, "Max Verstappen", "Red Bull Racing" },
{ 6, "Daniel Ricciardo", "Red Bull Racing" }
});
}

The database is created applying the migrations programmatically.

private static void SeedWithMigrations()
{
using (var context = new F1Context())
{
context.Database.Migrate();
Console.WriteLine("F1 database created, seeded, and updated");
}
}

view raw
Program.cs
hosted with ❤ by GitHub

Next, another property is added to the Racer type, the Point property. With the model creation functionality, the points for the racers now need to be included as well:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Racer>().Property(r => r.Name)
.IsRequired()
.HasMaxLength(100);
modelBuilder.Entity<Racer>().Property(r => r.Team)
.IsRequired(false)
.HasMaxLength(60);
modelBuilder.Entity<Racer>().HasData(
new Racer { Id = 1, Name = "Lewis Hamilton", Team = "Mercedes", Points = 231 },
new Racer { Id = 2, Name = "Sebastian Vettel", Team = "Ferrari", Points = 214 },
new Racer { Id = 3, Name = "Kimi Räikkönen", Team = "Ferrari", Points = 146 },
new Racer { Id = 4, Name = "Valtteri Bottas", Team = "Mercedes", Points = 144 },
new Racer { Id = 5, Name = "Max Verstappen", Team = "Red Bull Racing", Points = 120 },
new Racer { Id = 6, Name = "Daniel Ricciardo", Team = "Red Bull Racing", Points = 118 }
);
}

view raw
F1Context.cs
hosted with ❤ by GitHub

What about adding a migration now? With the migration, not only the Points column need to be added to the databases, but also the existing records from the database need to be updated to include the new values. Let’s apply the new migration:

dotnet ef migrations add AddPoints --context F1Context

Applying this migration creates the AddPoints migration class where the Points column is created, and the existing records based on their id updated with the new values for points:

public partial class AddPoints : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "Points",
table: "Racers",
nullable: false,
defaultValue: 0);
migrationBuilder.UpdateData(
table: "Racers",
keyColumn: "Id",
keyValue: 1,
column: "Points",
value: 231);
migrationBuilder.UpdateData(
table: "Racers",
keyColumn: "Id",
keyValue: 2,
column: "Points",
value: 214);
migrationBuilder.UpdateData(
table: "Racers",
keyColumn: "Id",
keyValue: 3,
column: "Points",
value: 146);
migrationBuilder.UpdateData(
table: "Racers",
keyColumn: "Id",
keyValue: 4,
column: "Points",
value: 144);
migrationBuilder.UpdateData(
table: "Racers",
keyColumn: "Id",
keyValue: 5,
column: "Points",
value: 120);
migrationBuilder.UpdateData(
table: "Racers",
keyColumn: "Id",
keyValue: 6,
column: "Points",
value: 118);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Points",
table: "Racers");
}
}

Summary

Data seeding is one of the great enhancements for EF Core 2.1. Seeding was already available with the .NET Framework version of the Entity Framework. Compared to the older implementation, the new version needed to be enhanced as Entity Framework didn’t support shadow state. This functionality is now available with data seeding for EF Core. The new implementation for EF Core also supports migrations. With Entity Framework, I didn’t use seeding data with many projects because migrations was not possible. This is now a feature with EF Core, and data seeding shines new.

If you read that far, it would be great if you support my writing by buying me a coffee. This helps staying up longer and writing more articles. You can also add information about what topics you’re in special interested in – I will consider this.

Buy Me A Coffee

My book Professional C# 7 and .NET Core 2.0 has a complete chapter dedicated to Entity Framework Core. Maybe you’re also interested in my Data Programming workshops.

Enjoy learning and programming!

Christian

4 thoughts on “Data Seeding with EF Core

  1. I like to thank you for this post. I’ve been searching for many ways to do this, but only with your example I could resolve my issue.

    Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.