Moving from EF6 to EF Core

I always liked using the latest and greatest and in most of the cases this choice comes as a great challenge. We started building a project a couple of years ago in ASP.NET vNext (or whatever they called it at the time) beta4 with all the latest tooling: dnvm, dnx, dnu, etc... We were not brave enough at the time to use Entity Framework 7 for obvious reasons, it was simply not ready, so we used EF6 and it was kind of happy days until we were ready to release the first version of the application and ready to run enable migrations then we hit a massive roadblock, EF6 tooling wasn't available for that version of .NET. Something that was supposed to take 5 minutes ended up taking the whole week, that wasn't happy days at all I gotta say. At the end, we found a solution on Gabriel Garcia's GitHub repository. He saved the day.

That was a bit of the background and paths we took. Now, as I'm writing this post, I'm facing a new challenge: Moving from EF6 to EF Core. I just started the migration and what I'm gonna do here is write down every change I make on the fly to get to the other side. When or If I get to the other end, I'll tidy up this post and publish it.

If you are not sure about moving, check this post to ensure that the changes won't cause you too much grief.

As per the Entity Framework Blog, they do NOT advise moving from EF6 to EF Core:

Because of the fundamental changes in EF Core we do not recommend attempting to move an EF6.x application to EF Core unless you have a compelling reason to make the change. You should view the move from EF6.x to EF Core as a port rather than an upgrade.

I'll do it all the same. Using EF6 in a .NET Core application is already a compelling enough reason to move. So here we go, these are the changes I had to make.

Replace Dependencies

Uninstall EF6 packages

EntityFramework

Install EF Core packages

Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.Design
Code Updates
  • Namespace System.Data.Entity replaced by Microsoft.EntityFrameworkCore
  • HasDatabaseGeneratedOption(DatabaseGeneratedOption.None) replaced by ValueGeneratedNever();
  • The base constructor of DbContext doesn't have a single string parameter for the connection string. We now have to inject the DbContextOptions
  • OnModelCreating(DbModelBuilder modelBuilder) becomes OnModelCreating(ModelBuilder modelBuilder). Simple change, but change all the same
  • modelBuilder.Configurations.AddFromAssembly(Assembly.GetExecutingAssembly()); is no longer available which means that EntityTypeConfiguration is also not available, so I had to move all my entity configuration to OnModelCreating
  • ((IObjectContextAdapter)context).ObjectContext.ObjectMaterialized is no longer available. I was using that to extend the DbContext to convert all dates in an out to Utc. I haven't found a replacement for that yet.
  • ComplexType is no longer available. I had to change the model structure a bit to accomodate this.
  • MigrateDatabaseToLatestVersion is no longer available so I had to add the below to my startup.cs
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
    serviceScope.ServiceProvider.GetService<SbDbContext>().Database.Migrate();
}
  • WillCascadeOnDelete(false) becomes OnDelete(DeleteBehavior.Restrict)
  • HasOptional is no longer relevant as per post
  • IDbSet becomes DbSet
  • DbSet<T>.Add() no longer returns T but EntityEntry<T>
var entry = context.LearningAreaCategories.Add(new LearningAreaCategory());
//that's if you need to use the entity afterwards
var entity = entry.Entity;
  • IQueryable<T>.Include(Func<>) now returns IIncludableQueryable<T,Y> instead of IQueryable<T>, same applies for OrderBy. What I did was moving all the includes and orderbys to the end.
var query = context.Students;
if(filter.StudentId > 0)
{
    query = query.Where(s => s.Id == filter.StudentId);
}
return query.Include(s => s.School).OrderBy(s => s.Name);
Migrations

We had plenty of existing EF6 migrations and, as per post, it's pretty much unfeasible to port EF6 migrations to EF Core. So I did what they suggested and deleted the entire Migrations folder and started fresh with a new EF Core migration with the command add-migration init-with-core. enable migrations is no longer required for EF Core as add-migration does its job.

1st attempt to run add-migration

Could not load file or assembly 'Microsoft.EntityFrameworkCore.Design' or one of its dependencies. The system cannot find the file specified.

2nd, 3rd, 4th attempts had the same issue then I restarted Visual Studio`

5th attempt I started getting

Unable to determine the relationship represented by navigation property 'Response.Students' of type 'ICollection<Student>'. Either manually configure the relationship, or ignore this property from the model.

Where it seems that EF Core doesn't automatically create the relationships, so I had to add the relationships one by one. I haven't decided yet if that's a good or a bad thing.

Finally, I got

PM> add-migration init-with-core
To undo this action, use Remove-Migration.
Other things worth mentioning
  • Foreign keys in EF Core generate shadow properties using the pattern [Entity]Id where in EF6 it uses [Entity]_Id. To avoid future conflicts, before moving to EF Core, add foreign keys as a normal property to the entity. More info about shadow properties here
public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }

    //foreign key added as a normal property
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}
  • If you're uncertain that EF Core and EF6 will generate the same database schema, use tools like SQL Compare to double check.

Hope it helps.

Cheers.