EF Core migration features that you might not know about
Published: Monday 26 August 2024
There are some invaluable features in Entity Framework that will help you with database migrations.
We'll reveal four features that you might not know about and how they can be used.
Entity type configuration
One way of configurating entities in Entity Framework Core is to override the OnModelCreating
method in your DbContext
and use the ModelBuilder
type parameter within it to select the entity and add the configuration.
C# coding challenges
However, there is another way you can do it which is arguably cleaner.
For each entity, you can set up the configuration in its own class.
We have created a SocialMedia
entity that we want to add to our DbContext
. The entity has an Id
and Name
property.
// SocialMedia.cs
namespace RoundTheCode.HelpfulEfCore.Entities;
public class SocialMedia
{
public int Id { get; set; }
public string? Name { get; set; }
}
We want to configure the Name
property so it has a maximum length of 50 characters when we migrate it in the database.
To do that, we add a SocialMediaConfiguration
class.
For this to work, this class needs to implement the IEntityTypeConfiguration
interface, passing in the entity that we are configuring as its generic type.
The IEntityTypeConfiguration
interface has a Configure
method which has an EntityTypeBuilder
type as the parameter. This is where we can add the configuration.
// SocialMediaConfiguration.cs
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using RoundTheCode.HelpfulEfCore.Entities;
namespace RoundTheCode.HelpfulEfCore.Configuration;
public class SocialMediaConfiguration : IEntityTypeConfiguration<SocialMedia>
{
public void Configure(EntityTypeBuilder<SocialMedia> builder)
{
builder.Property(s => s.Name)
.HasMaxLength(50);
}
}
In-order to add this configuration class to the DbContext
, we need to override the OnModelCreating
method within it.
This method has a ModelBuilder
instance as the parameter. We use that and call the ApplyConfigurationsFromAssembly
method, passing in the assembly location of the configuration class for each entity.
As the SocialMediaConfiguration
class has a namespace of RoundTheCode.HelpfulEfCore.Configuration
, we can load in the RoundTheCode.HelpfulEfCore
assembly, and pass that in as the parameter to the ApplyConfigurationsFromAssembly
method.
As a result, any other configuration classes that implement the IEntityTypeConfiguration
interface and are inside the RoundTheCode.HelpfulEfCore
assembly will automatically be configured to that DbContext
when adding a new migration.
// MyDbContext.cs
using System.Reflection;
public class MyDbContext : DbContext
{
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.Load("RoundTheCode.HelpfulEfCore"));
base.OnModelCreating(modelBuilder);
}
}
With this in place, we can open up the Package Manager Console in Visual Studio by going to Tools, NuGet Package Manager and Package Manager Console.
Then to add a migration to the DbContext
, call Add-Migration
and give it a name. For example:
Add-Migration SocialMediaTable
Calling the Update-Database
command in Package Manager Console will apply that migration to the database.
Seed data
Seed data is another tool that can be used with EF Core migrations. This populates the database with an initial set of data for an entity.
Going back to the SocialMediaConfiguration
class, we can call the HasData
method in the EntityTypeBuilder
type parameter to add this data to the database.
To demonstrate this, we are going to add Facebook, Instagram and LinkedIn into the SocialMedia
table, explicitly specifying the Id
for each of them.
// SocialMediaConfiguration.cs
public class SocialMediaConfiguration : IEntityTypeConfiguration<SocialMedia>
{
public void Configure(EntityTypeBuilder<SocialMedia> builder)
{
...
builder.HasData(new SocialMedia
{
Id = 1,
Name = "Facebook"
}, new SocialMedia
{
Id = 2,
Name = "Instagram"
}, new SocialMedia
{
Id = 3,
Name = "LinkedIn"
});
}
}
We can then add another migration script by opening the Package Manager Console and running the Add-Migration
command.
Add-Migration SocialMediaSeedData
To update the database, we then call Update-Database
in Package Manager Console.
Value converters
Value converters allows you to convert data into a specific format in the database, and convert it back when its used in the application.
To demonstrate this, we have created a Product
entity.
// Product.cs
public class Product
{
public int Id { get; set; }
public string? Name { get; set; }
public DateTime Created { get; set; }
}
This entity contains a Created
property which has a DateTime
type.
We'll create a value converter that will store the date and time in the database using the UTC timezone. When it's used in the application, we convert it back to the local timezone on the server.
To do this, we create a new DateTimeUtcConverter
class, inheriting the ValueConverter
class with two generic types.
As the format for the database record and the application are both DateTime
types, we use DateTime
for both generic types.
Then it's a case of creating a constructor for the DateTimeUtcConverter
class, calling a base constructor in ValueConverter
.
When saving the record to the database, we convert it to UTC. When we convert it back, we specify that the date and time is in UTC format and convert it to the local time on the server.
// DateTimeUtcConverter.cs
public class DateTimeUtcConverter : ValueConverter<DateTime, DateTime>
{
public DateTimeUtcConverter() : base(
date => date.ToUniversalTime(),
date => DateTime.SpecifyKind(date, DateTimeKind.Utc).ToLocalTime()
)
{
}
}
To configure the DateTimeUtcConverter
class on a property in an entity, we call it in the configuration class using the EntityTypeBuilder type and calling the HasConversion
method.
// ProductConfiguration.cs
public class ProductConfiguration : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.Property(s => s.Name)
.HasMaxLength(50);
builder.Property(s => s.Created)
.HasConversion(new DateTimeUtcConverter());
}
}
Run a SQL command
SQL commands can also be run when applying a migration.
First of all, we need to create an empty migration by running the Add-Migration
command in Package Manager Console.
Add-Migration ProductSql
From there, we can add the migration scripts to the Up
method by calling the Sql
method in the MigrationBuilder
parameter type.
In this example, we are inserting a couple of records to the Product
table.
// ProductSql.cs
public partial class ProductSql : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("INSERT INTO dbo.Product (Name, Created) VALUES ('Book', GETUTCDATE())");
migrationBuilder.Sql("INSERT INTO dbo.Product (Name, Created) VALUES ('Television', GETUTCDATE())");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
To run these SQL commands as part of the migration, you just call the Update-Database
command in Package Manager Console and it will apply the changes.
Watch the video
Watch our video where we go through each of these EF Core migration features and how they work.
As well as showing you how to set up a value converter, we'll also demonstrate it working in an ASP.NET Core Web API:
If you want to know more about Entity Framework Core migrations, you can watch our six-part series on how to Get Started with EF Core.
Not only does it show you how to add a migration, it also shows you how to create a DbContext
and how to insert, update and delete a record from the database.