What's New in EF Core 6.0

EF Core 6.0 is currently in development. This contains an overview of interesting changes introduced in each preview.

This page does not duplicate the plan for EF Core 6.0. The plan describes the overall themes for EF Core 6.0, including everything we are planning to include before shipping the final release.

EF Core 6.0 Preview 1

Tip

You can run and debug into all the preview 1 samples shown below by downloading the sample code from GitHub.

UnicodeAttribute

GitHub Issue: #19794. This feature was contributed by @RaymondHuy.

Starting with EF Core 6.0, a string property can now be mapped to a non-Unicode column using a mapping attribute without specifying the database type directly. For example, consider a Book entity type with a property for the International Standard Book Number (ISBN) in the form "ISBN 978-3-16-148410-0":

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }

    [Unicode(false)]
    [MaxLength(22)]
    public string Isbn { get; set; }
}

Since ISBNs cannot contain any non-unicode characters, the Unicode attribute will cause a non-Unicode string type to be used. In addition, MaxLength is used to limit the size of the database column. For example, when using SQL Server, this results in a database column of varchar(22):

CREATE TABLE [Book] (
    [Id] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NULL,
    [Isbn] varchar(22) NULL,
    CONSTRAINT [PK_Book] PRIMARY KEY ([Id]));

Note

EF Core maps string properties to Unicode columns by default. UnicodeAttribute is ignored when the database system supports only Unicode types.

PrecisionAttribute

GitHub Issue: #17914. This feature was contributed by @RaymondHuy.

The precision and scale of a database column can now be configured using mapping attributes without specifying the database type directly. For example, consider a Product entity type with a decimal Price property:

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

    [Precision(precision: 10, scale: 2)]
    public decimal Price { get; set; }
}

EF Core will map this property to a database column with precision 10 and scale 2. For example, on SQL Server:

CREATE TABLE [Product] (
    [Id] int NOT NULL IDENTITY,
    [Price] decimal(10,2) NOT NULL,
    CONSTRAINT [PK_Product] PRIMARY KEY ([Id]));

EntityTypeConfigurationAttribute

GitHub Issue: #23163. This feature was contributed by @KaloyanIT.

IEntityTypeConfiguration<TEntity> instances allow ModelBuilder configuration for a each entity type to be contained in its own configuration class. For example:

public class BookConfiguration : IEntityTypeConfiguration<Book>
{
    public void Configure(EntityTypeBuilder<Book> builder)
    {
        builder
            .Property(e => e.Isbn)
            .IsUnicode(false)
            .HasMaxLength(22);
    }
}

Normally, this configuration class must be instantiated and called into from DbContext.OnModelCreating. For example:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    new BookConfiguration().Configure(modelBuilder.Entity<Book>());
}

Starting with EF Core 6.0, an EntityTypeConfigurationAttribute can be placed on the entity type such that EF Core can find and use appropriate configuration. For example:

[EntityTypeConfiguration(typeof(BookConfiguration))]
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Isbn { get; set; }
}

This attribute means that EF Core will use the specified IEntityTypeConfiguration implementation whenever the Book entity type is included in a model. The entity type is included in a model using one of the normal mechanisms. For example, by creating a DbSet<TEntity> property for the entity type:

public class BooksContext : DbContext
{
    public DbSet<Book> Books { get; set; }

    //...

Or by registering it in OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Book>();
}

Note

EntityTypeConfigurationAttribute types will not be automatically discovered in an assembly. Entity types must be added to the model before the attribute will be discovered on that entity type.

Translate ToString on SQLite

GitHub Issue: #17223. This feature was contributed by @ralmsdeveloper.

Calls to ToString() are now translated to SQL when using the SQLite database provider. This can be useful for text searches involving non-string columns. For example, consider a User entity type that stores phone numbers as numeric values:

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public long PhoneNumber { get; set; }
}

ToString can be used to convert the number to a string in the database. We can then use this string with a function such as LIKE to find numbers that match a pattern. For example, to find all numbers containing 555:

var users = context.Users.Where(u => EF.Functions.Like(u.PhoneNumber.ToString(), "%555%")).ToList();

This translates to the following SQL when using a SQLite database:

SELECT COUNT(*)
FROM "Users" AS "u"
WHERE CAST("u"."PhoneNumber" AS TEXT) LIKE '%555%'

Note that translation of ToString() for SQL Server is already supported in EF Core 5.0, and may also be supported by other database providers.

EF.Functions.Random

GitHub Issue: #16141. This feature was contributed by @RaymondHuy.

EF.Functions.Random maps to a database function returning a pseudo-random number between 0 and 1 exclusive. Translations have been implemented in the EF Core repo for SQL Server, SQLite, and Cosmos. For example, consider a User entity type with a Popularity property:

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public int Popularity { get; set; }
}

Popularity can have values from 1 to 5 inclusive. Using EF.Functions.Random we can write a query to return all users with a randomly chosen popularity:

var users = context.Users.Where(u => u.Popularity == (int)(EF.Functions.Random() * 5.0) + 1).ToList();

This translates to the following SQL when using a SQL Server database:

SELECT [u].[Id], [u].[Popularity], [u].[Username]
FROM [Users] AS [u]
WHERE [u].[Popularity] = (CAST((RAND() * 5.0E0) AS int) + 1)

Support for SQL Server sparse columns

GitHub Issue: #8023.

SQL Server sparse columns are ordinary columns that are optimized to store null values. This can be useful when using TPH inheritance mapping where properties of a rarely used subtype will result in null column values for most rows in the table. For example, consider a ForumModerator class that extends from ForumUser:

public class ForumUser
{
    public int Id { get; set; }
    public string Username { get; set; }
}

public class ForumModerator : ForumUser
{
    public string ForumName { get; set; }
}

There may be millions of users, with only a handful of these being moderators. This means mapping the ForumName as sparse might make sense here. This can now be configured using IsSparse in OnModelCreating. For example:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<ForumModerator>()
        .Property(e => e.ForumName)
        .IsSparse();
}

EF Core migrations will then mark the column as sparse. For example:

CREATE TABLE [ForumUser] (
    [Id] int NOT NULL IDENTITY,
    [Username] nvarchar(max) NULL,
    [Discriminator] nvarchar(max) NOT NULL,
    [ForumName] nvarchar(max) SPARSE NULL,
    CONSTRAINT [PK_ForumUser] PRIMARY KEY ([Id]));

Note

Sparse columns have limitations. Make sure to read the SQL Server sparse columns documentation to ensure that sparse columns are the right choice for your scenario.

In-memory database: validate required properties are not null

GitHub Issue: #10613. This feature was contributed by @fagnercarvalho.

The EF Core in-memory database will now throw an exception if an attempt is made to save a null value for a property marked as required. For example, consider a User type with a required Username property:

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

    [Required]
    public string Username { get; set; }
}

Attempting to save an entity with a null Username will result in the following exception:

Microsoft.EntityFrameworkCore.DbUpdateException: Required properties '{'Username'}' are missing for the instance of entity type 'User' with the key value '{Id: 1}'.

This validation can be disabled if necessary. For example:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .LogTo(Console.WriteLine, new[] { InMemoryEventId.ChangesSaved })
        .UseInMemoryDatabase("UserContextWithNullCheckingDisabled")
        .EnableNullabilityCheck(false);
}

Improved SQL Server translation for IsNullOrWhitespace

GitHub Issue: #22916. This feature was contributed by @Marusyk.

Consider the following query:

var users = context.Users.Where(
    e => string.IsNullOrWhiteSpace(e.FirstName)
         || string.IsNullOrWhiteSpace(e.LastName)).ToList();

Before EF Core 6.0, this was translated to the following on SQL Server:

SELECT [u].[Id], [u].[FirstName], [u].[LastName]
FROM [Users] AS [u]
WHERE ([u].[FirstName] IS NULL OR (LTRIM(RTRIM([u].[FirstName])) = N'')) OR ([u].[LastName] IS NULL OR (LTRIM(RTRIM([u].[LastName])) = N''))

This translation has been improved for EF Core 6.0 to:

SELECT [u].[Id], [u].[FirstName], [u].[LastName]
FROM [Users] AS [u]
WHERE ([u].[FirstName] IS NULL OR ([u].[FirstName] = N'')) OR ([u].[LastName] IS NULL OR ([u].[LastName] = N''))

Database comments are scaffolded to code comments

GitHub Issue: #19113. This feature was contributed by @ErikEJ.

Comments on SQL tables and columns are now scaffolded into the entity types created when reverse-engineering an EF Core model from an existing SQL Server database. For example:

/// <summary>
/// The Blog table.
/// </summary>
public partial class Blog
{
    /// <summary>
    /// The primary key.
    /// </summary>
    [Key]
    public int Id { get; set; }
}

Microsoft.Data.Sqlite 6.0 Preview 1

Tip

You can run and debug into all the preview 1 samples shown below by downloading the sample code from GitHub.

Savepoints API

GitHub Issue: #20228.

We have been standardizing on a common API for savepoints in ADO.NET providers. Microsoft.Data.Sqlite now supports this API, including:

Using a savepoint allows part of a transaction to be rolled back without rolling back the entire transaction. For example, the code below:

  • Creates a transaction
  • Sends an update to the database
  • Creates a savepoint
  • Sends another update to the database
  • Rolls back to the savepoint previous created
  • Commits the transaction
using var connection = new SqliteConnection("DataSource=test.db");
connection.Open();

using var transaction = connection.BeginTransaction();

using (var command = connection.CreateCommand())
{
    command.CommandText = @"UPDATE Users SET Username = 'ajcvickers' WHERE Id = 1";
    command.ExecuteNonQuery();
}

transaction.Save("MySavepoint");

using (var command = connection.CreateCommand())
{
    command.CommandText = @"UPDATE Users SET Username = 'wfvickers' WHERE Id = 2";
    command.ExecuteNonQuery();
}

transaction.Rollback("MySavepoint");

transaction.Commit();

This will result in the first update being committed to the database, while the second update is not committed since the savepoint was rolled back before committing the transaction.

Command timeout in the connection string

GitHub Issue: #22505. This feature was contributed by @nmichels.

ADO.NET providers support two distinct timeouts:

  • The connection timeout, which determines the maximum time to wait when making a connection to the database.
  • The command timeout, which determines the maximum time to wait for a command to complete executing.

The command timeout can be set from code using DbCommand.CommandTimeout. Many providers are now also exposing this command timeout in the connection string. Microsoft.Data.Sqlite is following this trend with the Command Timeout connection string keyword. For example, "Command Timeout=60;DataSource=test.db" will use 60 seconds as the default timeout for commands created by the connection.

Tip

Sqlite treats Default Timeout as a synonym for Command Timeout and so can be used instead if preferred.