What Is a UX Audit and How to Conduct One to Improve Your Website's Usability
July 31, 2024
Moving from Entity Framework 6 to Entity Framework Core 2.0
Introduction
We recently took on a rather large web application development project and decided to take the plunge into .NET Core. Some co-workers had already done so earlier using .NET Core 1 with mixed results. With the release of .NET Core 2 and Visual Studio 2017, it seemed like a good time for our team to join them in the deep end. This post can be considered a follow-up to my earlier blog post, Getting Started with Entity Framework 6 Code First and will cover the issues we ran into using EF Core and some of the workarounds we came up with. I would recommend reading the earlier blog post first as it will provide a detailed background on the core concepts of code first which this blog post will not cover.
Keep in mind while reading this post that we used EF Core 2.0 for this project. At the time of this writing, that was the latest version of EF Core, but EF Core 2.1 is due to be released in the second quarter of 2018 and things will certainly change with that release.
EF Core vs EF 6
EF 6 is a stable and mature ORM while EF Core is relatively new. Microsoft rebuilt EF Core from the ground up and removed many of the internal dependencies and providers that EF 6 had (like SQLClient). In the long run, that will make EF Core much more extensible and lighter weight. In the short run, we’ll have to deal with some pain as some features and command we relied on in EF 6 are not implemented yet and some may never be. If you are interested in what the EF team is working on for future releases, you can check out the EF Core Roadmap.
What’s Missing in EF Core
In this section I’m going to cover the areas that affected us the most when we started using EF Core. For a full feature by feature comparison, see the Microsoft Docs page EF Core and EF6 Feature by Feature Comparison.
Data Annotations
In EF 6 you had a choice with configuring your entities to use Data Annotation Schema Attributes, Fluent API, or a combination of the two. As a personal design goal, I tried to configure everything with Data Annotations. My thinking is it made the entity self-describing and you only had to go to one class file to see how the entity was configured instead of bouncing back and forth between the entity class and the context class.
1. In EF Core, you still have this choice although the options for configuring using Data Annotations have lessened a bit. An example of this is the Index attribute which allowed you to specify the creation of a SQL Server Index on an entity property.
[StringLength(100), Required, Index("IX_User_Username", IsUnique = true)]
public string Username { get; set; }
The Index Attribute is not supported in EF Core, so you must use the Fluent API to configure SQL Server Indexes as show below.
modelBuilder.Entity<User>().HasIndex(u => u.Username).IsUnique();
Join Tables
In EF 6 you could have a many to many join table by convention without creating an entity for it by including a navigation property in both entities. In the example below we have a User, containing the users for our application, and Role table, containing the security roles that a user can have in the application.
public class User
{
public Guid RoleId { get; set; }
[Required, StringLength(20), Index("IX_Role_Name", IsUnique = true)]
public string Name { get; set; }
[StringLength(512)]
public string Description { get; set; }
public bool Active { get; set; } = true;
public Enums.RoleCode RoleCode { get; set; }
public virtual ICollection<User> Users { get; set; }
}
When a migration is added, a join table is created based on the two virtual properties in the entity as you can see below.
CreateTable(
"dbo.UserRole",
c => new
{
User_UserId = c.Guid(nullable: false),
Role_RoleId = c.Guid(nullable: false),
})
.PrimaryKey(t => new { t.User_UserId, t.Role_RoleId })
.ForeignKey("dbo.User", t => t.User_UserId, cascadeDelete: true)
.ForeignKey("dbo.Role", t => t.Role_RoleId, cascadeDelete: true)
.Index(t => t.User_UserId)
.Index(t => t.Role_RoleId);
In EF Core 2, many to many relationships without an explicit join table are not supported. To work around this, you must create an entity for the join table and specify what is being joined using the Fluent API.
public class UserRole
{
public long UserId { get; set; }
public long RoleId { get; set; }
public Role Role { get; set; }
public User User { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// setup many to many relationship between user and role
modelBuilder.Entity<UserRole>()
.HasKey(t => new { t.UserId, t.RoleId });
modelBuilder.Entity<UserRole>()
.HasOne(pt => pt.User)
.WithMany(p => p.AssignedRoles)
.HasForeignKey(pt => pt.UserId);
modelBuilder.Entity<UserRole>()
.HasOne(pt => pt.Role)
.WithMany(t => t.Users)
.HasForeignKey(pt => pt.RoleId);
Mapping Entities to SQL Server Views
I find mapping an entity to a SQL Server view is useful in situations where you have need to represent a flattened view that requires a complex or expensive query through EF. A good workaround is to create a view in SQL Server then map the entity to the view using the Table attribute. With the Table attribute in place, EF treats the view the same as a SQL Server table.
[Table("vwCompanyList", Schema = "dbo")]
public class CompanyList : EntityBase
{
[Key]
public long CompanyId { get; set; }
Unfortunately, EF Core does not support mapping an entity to a SQL Server View. There is a workaround that can be used to trick EF into mapping the SQL Server View as a table, but it has some limitations. You can use a raw SQL query that maps into an entity you create that matches the columns in the SQL Server View.
public IQueryable<PaymentBatchSearch> GetPaymentBatches()
{
// EF Core does not support SQL Server View so have to use FromSql for now
//IQueryable<PaymentBatchSearch> paymentBatches = _context.PaymentBatchSearches;
IQueryable<PaymentBatchSearch> paymentBatches = _context.PaymentBatchSearches.FromSql("SELECT * FROM vwPaymentBatchSearch");
return paymentBatches;
}
The biggest pain point with this approach is EF Core will attempt to generate a migration for the entity you create for the SQL Server View. The NotMapped attribute would seem like a good way to get EF Core to not generate the migration but EF Core seems to ignore it. You can work around this using the Fluent API and telling the ModelBuilder to ignore the entity.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// ignore views when creating a migration
// comment out after creating a migration
modelBuilder.Ignore<CompanyList>();
That works great for not having your entity generated as a table in SQL Server, however when you attempt to use that entity in your code, you’ll end up getting an Exception “Cannot create a DbSet for 'CompanyList' because this type is not included in the model for the context.”.
What we ended up doing was commenting out the ModelBuilder Ignore, then un-commenting it before a migration. After the migration we commented it out again. As you can imagine, half the time we’d forget to comment it out leading EF Core to create a new table in the migration or we’d forget to comment it out after the migration leading to the exception. So, this solution is less than optimal, but it is the only workaround for now.
Power Tools
Entity Framework 6 Power Tools provided some great functionality when working with code first and EF Core Power Tools does the same. View DbContext Model DDL SQL is a useful feature for deployment as it generates a SQL CREATE script for your POCO classes.
Conclusion
I would recommend using EF Core for your .NET Core applications. There are a few pain points but it if you’ve been using EF 6, you’ll find it very easy to pick up and work around a few of the limitations we ran into.
Further Reading
Entity Framework Core
https://docs.microsoft.com/ef/core/
Compare EF Core & EF6.x
https://docs.microsoft.com/ef/efcore-and-ef6/index
New features in EF Core 2.0
https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-2.0
Getting started with ASP.NET Core and Entity Framework Core using Visual Studio
https://docs.microsoft.com/aspnet/core/data/ef-mvc/
Implementing the infrastructure persistence layer with Entity Framework Core
https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-implemenation-entity-framework-core