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.
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"} | |
); | |
} | |
} |
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
:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using (var context = new BooksContext()) | |
{ | |
bool created = context.Database.EnsureCreated(); | |
Console.WriteLine($"Books database created and seeded: {created}"); | |
} |
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
:
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
:
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"} | |
); | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private static void SeedWithMigrations() | |
{ | |
using (var context = new F1Context()) | |
{ | |
context.Database.Migrate(); | |
Console.WriteLine("F1 database created, seeded, and updated"); | |
} | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | |
); | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
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
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.
LikeLiked by 1 person
Thiago, you’re welcome. Thanks for the feedback.
LikeLike