Entity Framework Core 5.0 - An Introduction To What's New

Introduction

In this article, we will discuss the Entity Framework Core 5.0. Entity Framework is nothing new, however, with .NET Core 5.0, Microsoft introduced many new features or functionality in the Entity Framework which will help developers during the development process. Entity Framework Core is a modern object-based database mapped for the .NET Core. It supports many things like LINQ queries, changes tracking, updates of data models, and schema-based migration. Entity Framework Core can work with many databases like SQL Database (both on-premises and Azure version), SQLite MySQL, PostgreSQL, and Azure Cosmos DB. Entity Framework (EF) Core is a lightweight, extensible, open-source, and cross-platform-based version of the existing Entity Framework data access technology. In this article, we will discuss related to the new features introduced in the .NET Core 5.0.

Overview of Entity Framework

In the .NET 3.5 version, developers mainly used or wrote down the ADO.NET-related codes to save or retrieve application data from the database. For that purpose, we need to open a connection to the database, create a Dataset to fetch or send the data to the database, and then convert the Dataset to .NET Objects or vice versa. This entire process is a very lengthy and error-prone process. As an alternative to this approach, Microsoft provides a framework called Entity Framework which can automate all these database-related activities for our application.

Entity Framework is an open-source ORM-based framework for based applications. With the help of this framework, developers can work with data using the objects of domain-specific classes without focusing on the related database tables and columns. With the help of Entity Framework, the developer can work at a higher level of abstraction when they deal with the data and can create and maintain data-oriented applications by writing less code compared to traditional applications.

Businesslayer

As shown in the above figure, the Entity Framework fits between the business entities (domain classes) and the database. It saves data stored in the properties of business entities retrieves data from the database and converts it to business entity objects automatically.

Features of Entity Framework

Entity Framework has several features that help developers develop any application by writing less coding. Some of the most important features are as follows.

  1. Entity Framework is a cross-platform-based framework that can run on Windows, Linux, and Mac at the same time.
  2. Entity Framework always creates EDM (Entity Data Model) based entities with getting/set properties of different data types. This entity is used by the framework at the time of querying or saving data to the database.
  3. Entity Framework supports LINQ Queries to retrieve the data from the database. This framework also allows us to execute the raw SQL queries directly to the database to perform query operations.
  4. Entity Framework always executes INSERT, UPDATE, and DELETE commands to the database based on the changes that occurred to our entities when we call the SaveChanges() method. Entity Framework also provides the asynchronous SaveChangesAsync() method.
  5. Entity Framework always performs automatic transaction management while querying or saving data. It also provides options to customize transaction management.
  6. Entity Framework provides the first level of caching out of the box. So, repeated querying will return data from the cache instead of hitting the database.
  7. Entity Framework provides to configure the Entity Framework model by using data annotation attributes or Fluent API to override default conventions.

Overview of Entity Framework Core 5.0

Entity Framework Core is the new version of the Entity Framework after EF 6.0. EF Core is an open-source, lightweight, extensible, and cross-platform-based version of Entity Framework Data access technology. Entity Framework is an Object/Relational Mapping (ORM) based framework. It is an enhancement of the ADO.NET which provides developers with an automated mechanism for accessing and storing the data into the database. Entity Framework core is mainly used to develop the .NET Core-based application. Despite that, we can develop the standard .NET 4.5 or above-based applications using the Entity Framework Core. The below image demonstrates the supported application types, .Net Framework, and Operating Systems.

Entity Framework

Entity Framework Core 5.0 is the new and improved version of the Entity Framework for the .NET Core applications. Since it is the new version, still it does not become so mature just like EF 6.0. EF Core continues to support the following features and concepts as compared to the EF 6.0.

  1. DBSet & DBContext
  2. Data Model
  3. Querying using Linq-To-Entities
  4. Change Tracking
  5. SaveChanges
  6. Migrations

However, there are some features or functionality which will not be supported in the Entity Framework Core, as shown below.

  1. Graphical Visualization of Model
  2. Entity Data Model Wizard for the DB-First Approach
  3. ObjectContext API or Querying using Entity SQL.
  4. Automated Migration
  5. Inheritance: TPT (Table Per type) & TPC (Table per Concrete Class)
  6. Entity Splitting
  7. Spatial Data
  8. Lazy loading of related data
  9. Store Procedure mapping with DBContext for the CRUD operations
  10. Seed Data.

Also, Entity Framework Core provides the following new features or functionality that are not supported in EF 6.0.

  1. Batch Insert, Update, and Delete Operations
  2. In-Memory Provider for Testing
  3. Support for the IoC (Inversion of Control)
  4. Implementation of Unique Constraint
  5. Shadow Properties
  6. Alternate Keys
  7. Global Query Filter
  8. Field Mapping
  9. DBContext Pooling
  10. Better patterns for Handling disconnected entity graphs.

EF Core Database Providers

Entity Framework Core uses a provider model to access many different databases. EF Core includes providers as NuGet packages which you need to install. The following table lists database providers and NuGet packages for EF Core.

Database Name NuGet Package Name
SQL Server Microsoft.EntityFrameworkCore.SqlServer
MYSQL MySql.Data.EntityFrameworkCore
PostgreSQL Npgsql.EntityFrameworkCore.PostgreSQL
SQLite Microsoft.EntityFrameworkCore.SQLite
SQL Compact EntityFrameworkCore.SqlServerCompact40
In-Memory Microsoft.EntityFrameworkCore.InMemory


What’s New in EF Core 5.0?

Since Entity Framework Core 5.0 is the major release, it contains many new features in the release. EF Core 5.0 release contains many breaking changes which are mainly for the API improvement or the behavioral changes related to the existing applications.

Many–to–Many

In Entity Framework 5.0, one of the main features is the Many–to–many relationships without explicitly mapping the join table. In the previous version of EF Core, we need to define the third entity to establish the many-to-many relationship. As per the example, if we want to establish a many-to-many relationship between Author Entity and Blogs Entity, then we need to define three entities – Author, Blog, and AuthorBlog. But in EF Core 5.0, it is not necessary to define the third entity i.e. AuthorBlog. Now, we can define the Many-to-Many relationship between the Author and Blog entities. To define the many-to-many relationships between these two entities, we need to define the mentioned entity class, as shown below.

public class Author  
{  
    public int Id { get; set; }  

    public string Name { get; set; }  

    /* EF Relations */  
    public ICollection<Blog> Blogs { get; set; }  
}  

public class Blog  
{  
    public int Id { get; set; }  

    public string Text { get; set; }  

    /* EF Relations */  
    public ICollection<Author> Authors { get; set; }  
}  

In the above example, the Author contains a collection of Blogs and the Blog contains a collection of Authors. In Entity Framework 5.0, this is recognized as a many-to-many relationship by convention. So, the DBContext class as per the above entities will look like this.

public class EFCore5RelationshipsExamplesDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=(localdb)\MSSQLLocalDB;Database=TestDB;Trusted_Connection=True;MultipleActiveResultSets=true");
    }

    public DbSet<Author> Authors { get; set; }
    public DbSet<Blog> Blogs { get; set; }
}

Split Queries

From the Entity Framework Core 3.0, EF Core always populates a single SQL query for each LINQ query. This normally ensures the consistency of the data returned within the constraint of the transaction mode. However, this can become very slow when the query uses Include or a projection to bring back multiple related records or collections. But EF Core 5.0, now allows a single LINQ query with includes related collections that can be split into multiple SQL queries. This can significantly improve the performance of the application. Entity Framework now allows us to specify that to mention in the LINQ query whether it should split into multiple SQL queries or not. If the split is mentioned, then instead of JOINs, split queries generate an additional SQL query for each included collection.

using (var context = new BloggingContext())
{
    var blogs = context.Blogs
        .Include(blog => blog.Posts)
        .AsSplitQuery()
        .ToList();
}

It will produce the following SQL queries.

SELECT [b].[Id] as BlogId, [b].[Name]
FROM [Blogs] AS [b]
ORDER BY [b].[BlogId]

SELECT [p].[Id] as AuthorId, [p].[BlogId], [p].[Title], [b].[BlogId]
FROM [Blogs] AS [b]
INNER JOIN [Author] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]

We can also configure the split queries as a default in our application context.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\MSSQLLocalDB;Database=TestDB;Trusted_Connection=True;ConnectRetryCount=0",
                      o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
}

Logging & Diagnostics

Entity Framework Core 5.0 also introduces a simple and easy way to set up logging via the new LogTo method. Through this method, we can log the messages to the console including all SQL generated by EF Core. With the help of this method, we can generate logs from the EF Core application without any special kind of configuration related to the external logging framework.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.LogTo(Console.WriteLine);

There are multiple overloads available that can be used in different use cases.

  • Set the minimum log level
    • Example
      .LogTo(Console.WriteLine, LogLevel.Information)
      
  • Filter for only specific events
    • Example
      .LogTo(Console.WriteLine, new[] { CoreEventId.ContextInitialized, RelationalEventId.CommandExecuted })
      
  • Filter for all events in specific categories
    • Example
      .LogTo(Console.WriteLine, new[] { DbLoggerCategory.Database.Name }, LogLevel.Information)
      
  • Use a custom filter over event and level
    • Example
      .LogTo(Console.WriteLine, (id, level) => id == RelationalEventId.CommandExecuting)
      

Table–per–type (TPT) mapping

In general, Entity Framework maps as an inheritance hierarchy of .NET types into a single database type. It is normally known as table-per-hierarchy (TPH) mapping. But EF Core 5.0, allows us to map each .NET class type within an inheritance hierarchy into a different database table, it is known as table-per-type (TPT) mapping. Table-per-type inheritance normally uses a separate table in the database to maintain data for non-inherited properties and key properties for each type in the inheritance hierarchy.

  • Table-per-Type (TPT) normally represents inheritance relationships as a relational foreign key in the table.
  • Every class and subclass including abstract classes has its table.
  • The table for subclasses contains columns only for each non-inherited property along with a primary key that is also a foreign key of the base class table.

Let's consider the following simple class model with a mapped hierarchy.

public class Person
{
    public int Id { get; set; }
    public string FullName { get; set; }
}

public class Student : Person
{
    public DateTime EnrollmentDate { get; set; }
}

public class Teacher : Person
{
    public DateTime JoiningDate { get; set; }
}

By default, Entity Framework will map this with a single table.

CREATE TABLE [dbo].[People] (
    [Id] INT IDENTITY (1, 1) NOT NULL,
    [FullName] NVARCHAR(MAX) NULL,
    [Discriminator] NVARCHAR(MAX) NOT NULL,
    [EnrollmentDate] DATETIME2(7) NULL,
    [JoiningDate] DATETIME2(7) NULL,
    CONSTRAINT [PK_People] PRIMARY KEY CLUSTERED ([Id] ASC)
);

In the Table-per-Type mapping pattern, all the entity types are mapped with individual tables. Properties that belong solely to a base type or derived type are stored in a table that maps to that type. Entity types can be mapped to different tables using mapping attributes.

[Table("People")]
public class Person
{
    public int Id { get; set; }
    public string FullName { get; set; }
}

[Table("Students")]
public class Student : Person
{
    public DateTime EnrollmentDate { get; set; }
}

[Table("Teachers")]
public class Teacher : Person
{
    public DateTime JoiningDate { get; set; }
}

Now, mapping each entity type to a different table will instead result in one table per type.

CREATE TABLE [dbo].[People] (
    [Id] INT IDENTITY (1, 1) NOT NULL,
    [FullName] NVARCHAR(MAX) NULL,
    CONSTRAINT [PK_People] PRIMARY KEY CLUSTERED ([Id] ASC)
);

CREATE TABLE [dbo].[Students] (
    [Id] INT NOT NULL,
    [EnrollmentDate] DATETIME2(7) NOT NULL,
    CONSTRAINT [PK_Students] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_Students_People_Id] FOREIGN KEY ([Id]) REFERENCES [dbo].[People] ([Id])
);

CREATE TABLE [dbo].[Teachers] (
    [Id] INT NOT NULL,
    [JoiningDate] DATETIME2(7) NOT NULL,
    CONSTRAINT [PK_Teachers] PRIMARY KEY CLUSTERED ([Id] ASC),
    CONSTRAINT [FK_Teachers_People_Id] FOREIGN KEY ([Id]) REFERENCES [dbo].[People] ([Id])
);

Flexible Entity Mapping

In Entity Framework, Entity types are mainly mapped to the tables or view so that EF Core will pull the records of the table or view when querying for that type. In EF Core 5.0, now we have additional mapping options, in which we can map an entity with a SQL query (known as defining query) or a table-valued function (TVF).

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Post>().ToSqlQuery(@"SELECT Id, Name, Category, BlogId FROM posts");

    modelBuilder.Entity<Blog>().ToFunction("BlogsReturningFunction");
}

Table-valued function or TVF can also be mapped with a .NET method rather than to a DBSet. Now, in EF Core 5.0, it is possible to map an entity with a view when we perform a query, as shown below.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<Blog>()
        .ToTable("Blogs")
        .ToView("BlogsView");
}

Shared–type entity types with Property Bags

In Entity Framework Core 5.0, we can define or map the same CLR type to multiple different entity types, such types are normally known as shared-type entity types. While we are using any CLR type with this feature, .NET Dictionary offers a particularly compelling use case which we can call property-bags. These entities can be used to perform query or update operations just like other normal entity types with their own, dedicated CLR types. Normally, entity types that contain only indexer properties are called property bag entity types. These types of entities don’t have any shadow properties. Currently, only Dictionary<string, object> is supported as a property bag entity type. It needs to be defined as a shared entity type with a unique name and the related DBSet property must be implemented using Set call.

public class MyContext : DbContext
{
    public DbSet<Dictionary<string, object>> Blogs => Set<Dictionary<string, object>>("Blog");

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.SharedTypeEntity<Dictionary<string, object>>(
            "Blog", bb =>
            {
                bb.Property<int>("BlogId");
                bb.Property<string>("Url");
                bb.Property<DateTime>("LastUpdated");
            });
    }
}

Required 1:1 Dependents

Entity Framework Core always allows us to model the entity types which can only appear on navigation properties of other entity types. These are known as owned entity types. The entity contains an owned entity type is known as the owner. This was most apparent when using owned entities, as all the owned entity's columns were created as nullable in the database, even if they were configured as required in the model. But now, in EF Core 5.0, navigation to an owned entity can be configured as a required dependency. This way, when we migrate that entity to the database level, it creates NOT NULL columns in the database table.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Author>(b =>
    {
        b.OwnsOne(e => e.HomeAddress,
            b =>
            {
                b.Property(e => e.City).IsRequired();
                b.Property(e => e.Postcode).IsRequired();
            });
        b.Navigation(e => e.HomeAddress).IsRequired();
    });
}

DBContextFactory 

In Entity Framework Core 5.0, Microsoft introduced AddDbContextFactory and AddPooledDbContextFactory to register a factory for creating DbContext instances in the application's dependency injection (D.I.) container. This can be useful when the application code needs to create and dispose of context instances manually.

services.AddDbContextFactory<AuthorDbContext>(b =>
    b.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=TestDB"));

At this point, application services such as ASP.NET Core controllers can then be injected with IDbContextFactory<TContext>, and used to instantiate context instances.

public class MyController
{
    private readonly IDbContextFactory<SomeDbContext> _contextFactory;

    public MyController(IDbContextFactory<AuthorDbContext> contextFactory)
        => _contextFactory = contextFactory;

    public void DoSomeThing()
    {
        using (var context = _contextFactory.CreateDbContext())
        {
            
        }
    }
}

Conclusion

Entity Framework Core 5.0 has been released with many new features and functionality. In this article, we discuss the overview and features of Entity Framework. We also discussed the overview of the Entity Framework Core, its supported database providers, and key new release functionalities in EF Core 5.0. In the next article, we will discuss some of the new releases in detail along with examples. Any suggestions or feedback or queries related to this article are most welcome.