Partilhar via


Alterações significativas no EF Core 8 (EF8)

Esta página documenta as alterações de API e comportamento que têm o potencial de interromper a atualização de aplicativos existentes do EF Core 7 para o EF Core 8. Certifique-se de revisar as alterações de quebra anteriores se estiver atualizando de uma versão anterior do EF Core:

Estrutura de Destino

O EF Core 8 tem como alvo o .NET 8. Os aplicativos destinados a versões mais antigas do .NET, .NET Core e .NET Framework precisarão ser atualizados para o .NET 8 de destino.

Resumo

Quebrando a mudança Impacto
Contains em consultas LINQ podem parar de funcionar em versões mais antigas do SQL Server Alto
Possíveis regressões de desempenho de consulta em torno de Contains em consultas LINQ Alto
Enums em JSON são armazenados como ints em vez de strings por padrão Alto
SQL Server date e time agora geram scaffolding para o .NET DateOnly e TimeOnly Média
colunas booleanas com um valor gerado pelo banco de dados não são mais estruturadas como anuláveis Média
métodos SQLite Math agora se traduzem em SQL Baixo
ITypeBase substitui IEntityType em algumas APIs Baixo
expressões do ValueGenerator devem usar APIs públicas Baixo
ExcludeFromMigrations não exclui mais outras tabelas em uma hierarquia TPC Baixo
As chaves inteiras não sombreadas são armazenadas em documentos do Azure Cosmos DB Baixo
modelo relacional é gerado no modelo compilado Baixo
Os andaimes podem gerar diferentes nomes de navegação Baixo
Os discriminadores têm agora um comprimento máximo Baixo
valores chave do SQL Server são comparados sem distinção de maiúsculas/minúsculas Baixo
Várias chamadas AddDbContext são aplicadas em ordem diferente Baixo
EntityTypeAttributeConventionBase substituído por TypeAttributeConventionBase Baixo

Alterações de alto impacto

Contains em consultas LINQ podem parar de funcionar em versões mais antigas do SQL Server

Questão para Rastreio #13617

Comportamento antigo

EF tinha suporte especializado para consultas LINQ usando o operador Contains sobre uma lista de valores parametrizada:

var names = new[] { "Blog1", "Blog2" };

var blogs = await context.Blogs
    .Where(b => names.Contains(b.Name))
    .ToArrayAsync();

Antes do EF Core 8.0, o EF inseria os valores parametrizados como constantes no SQL:

SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2')

Novo comportamento

A partir do EF Core 8.0, o EF agora gera SQL que é mais eficiente em muitos casos, mas não tem suporte no SQL Server 2014 e abaixo:

SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (
    SELECT [n].[value]
    FROM OPENJSON(@__names_0) WITH ([value] nvarchar(max) '$') AS [n]
)

Observe que as versões mais recentes do SQL Server podem ser configuradas com um nível de compatibilidade mais antigo, tornando-as também incompatíveis com o novo SQL. Isso também pode ocorrer com um banco de dados SQL do Azure que foi migrado de uma instância anterior do SQL Server local, carregando o nível de compatibilidade antigo.

Porquê

A inserção de valores constantes no SQL cria muitos problemas de desempenho, derrotando o cache do plano de consulta e causando remoções desnecessárias de outras consultas. A nova conversão do EF Core 8.0 usa a função SQL Server OPENJSON para transferir os valores como uma matriz JSON. Isso resolve os problemas de desempenho inerentes à técnica anterior; no entanto, a função OPENJSON não está disponível no SQL Server 2014 e abaixo.

Para obter mais informações sobre essa alteração, consulte esta postagem de blog.

Atenuações

Se o seu banco de dados for SQL Server 2016 (13.x) ou mais recente, ou se você estiver usando o Azure SQL, verifique o nível de compatibilidade configurado do seu banco de dados por meio do seguinte comando:

SELECT name, compatibility_level FROM sys.databases;

Se o nível de compatibilidade estiver abaixo de 130 (SQL Server 2016), considere modificá-lo para um valor mais recente (documentação).

Caso contrário, se a versão do banco de dados realmente for mais antiga que o SQL Server 2016 ou estiver definida para um nível de compatibilidade antigo que você não pode alterar por algum motivo, você poderá configurar o EF para reverter para o SQL anterior ao 8.0 mais antigo. Se você estiver usando o EF 9, poderá usar o TranslateParameterizedCollectionsToConstantsrecém-introduzido:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.UseSqlServer("<CONNECTION STRING>", o => o.TranslateParameterizedCollectionsToConstants())

Se você estiver usando o EF 8, poderá obter o mesmo efeito ao usar o SQL Server configurando o nível de compatibilidade SQL do EF:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlServer(@"<CONNECTION STRING>", o => o.UseCompatibilityLevel(120));

Possíveis regressões de desempenho de consulta em torno de Contains em consultas LINQ

Tracking Issue #32394

Comportamento antigo

EF tinha suporte especializado para consultas LINQ usando o operador Contains sobre uma lista de valores parametrizável.

var names = new[] { "Blog1", "Blog2" };

var blogs = await context.Blogs
    .Where(b => names.Contains(b.Name))
    .ToArrayAsync();

Antes do EF Core 8.0, o EF inseria os valores parametrizados como constantes no SQL:

SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2')

Novo comportamento

A partir do EF Core 8.0, o EF agora gera o seguinte:

SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (
    SELECT [n].[value]
    FROM OPENJSON(@__names_0) WITH ([value] nvarchar(max) '$') AS [n]
)

No entanto, após o lançamento do EF 8, descobriu-se que, embora o novo SQL seja mais eficiente para a maioria dos casos, ele pode ser dramaticamente menos eficiente em uma minoria de casos, causando até mesmo tempos limite de consulta em alguns casos

Atenuações

Se estiver a usar o EF 9, pode usar o TranslateParameterizedCollectionsToConstants recém-introduzido para reverter a tradução de Contains para todas as consultas para restaurar o comportamento anterior ao 8.0:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.UseSqlServer("<CONNECTION STRING>", o => o.TranslateParameterizedCollectionsToConstants())

Se você estiver usando o EF 8, poderá obter o mesmo efeito ao usar o SQL Server configurando o nível de compatibilidade SQL do EF:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlServer(@"<CONNECTION STRING>", o => o.UseCompatibilityLevel(120));

Finalmente, é possível controlar a tradução para cada consulta individualmente usando EF.Constant da seguinte forma:

var blogs = await context.Blogs
    .Where(b => EF.Constant(names).Contains(b.Name))
    .ToArrayAsync();

Enums em JSON são armazenados como ints em vez de strings por padrão

Tracking Issue #13617

Comportamento antigo

No EF7, os enums mapeados para JSON são, por padrão, armazenados como valores de texto nos ficheiros JSON.

Novo comportamento

A partir do EF Core 8.0, o EF agora, por padrão, mapeia enums para valores inteiros no documento JSON.

Porquê

O EF sempre mapeou enums para uma coluna numérica como padrão em bancos de dados relacionais. Como o EF oferece suporte a consultas em que os valores de JSON interagem com valores de colunas e parâmetros, é importante que os valores em JSON correspondam aos valores na coluna não-JSON.

Atenuações

Para continuar usando cadeias de caracteres, configure a propriedade enum com uma conversão. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().Property(e => e.Status).HasConversion<string>();
}

Ou, para todas as propriedades do tipo enum::

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Properties<StatusEnum>().HaveConversion<string>();
}

Alterações de impacto médio

SQL Server date e time agora criam estrutura para .NET DateOnly e TimeOnly

Tracking Issue #24507

Comportamento antigo

Anteriormente, ao criar um scaffold em um banco de dados do SQL Server com colunas date ou time, o EF gerava propriedades de entidade com os tipos DateTime e TimeSpan.

Novo comportamento

A partir do EF Core 8.0, date e time são gerados como DateOnly e TimeOnly.

Porquê

DateOnly e TimeOnly foram introduzidas no .NET 6.0 e são uma combinação perfeita para mapear os tipos de data e hora do banco de dados. DateTime contém notavelmente um componente de tempo que não é usado e pode causar confusão ao mapeá-lo para date, e TimeSpan representa um intervalo de tempo - possivelmente incluindo dias - em vez de uma hora do dia em que um evento ocorre. Usar os novos tipos evita bugs e confusão, e fornece clareza de intenção.

Atenuações

Essa alteração afeta apenas os utilizadores que regularmente reconstroem o scaffold da sua base de dados num modelo de código EF ("fluxo de banco de dados primeiro").

Recomenda-se reagir a essa alteração modificando seu código para usar os tipos DateOnly e TimeOnly recém-estruturados. No entanto, se isso não for possível, você pode editar os modelos de andaimes para reverter para o mapeamento anterior. Para fazer isso, configure os modelos conforme descrito em esta página. Em seguida, edite o arquivo EntityType.t4, localize onde as propriedades da entidade são geradas (pesquise property.ClrType) e altere o código para o seguinte:

        var clrType = property.GetColumnType() switch
        {
            "date" when property.ClrType == typeof(DateOnly) => typeof(DateTime),
            "date" when property.ClrType == typeof(DateOnly?) => typeof(DateTime?),
            "time" when property.ClrType == typeof(TimeOnly) => typeof(TimeSpan),
            "time" when property.ClrType == typeof(TimeOnly?) => typeof(TimeSpan?),
            _ => property.ClrType
        };

        usings.AddRange(code.GetRequiredUsings(clrType));

        var needsNullable = Options.UseNullableReferenceTypes && property.IsNullable && !clrType.IsValueType;
        var needsInitializer = Options.UseNullableReferenceTypes && !property.IsNullable && !clrType.IsValueType;
#>
    public <#= code.Reference(clrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#

As colunas booleanas com um valor gerado pelo banco de dados não são mais estruturadas como anuláveis

Problema de rastreamento #15070

Comportamento antigo

Anteriormente, colunas não anuláveis de bool com uma restrição padrão do banco de dados eram geradas automaticamente como propriedades de bool? passíveis de ser nulas.

Novo comportamento

A partir do EF Core 8.0, as colunas de bool não anuláveis são sempre estruturadas como propriedades não anuláveis.

Porquê

Uma propriedade bool não terá seu valor enviado para o banco de dados se esse valor for false, que é o padrão CLR. Se o banco de dados tiver um valor padrão de true para a coluna, mesmo que o valor da propriedade seja false, o valor no banco de dados terminará como true. No entanto, no EF8, a sentinela usada para determinar se uma propriedade tem um valor pode ser alterada. Isso é feito automaticamente para bool propriedades com um valor gerado pelo banco de dados de true, o que significa que não é mais necessário andaimear as propriedades como anuláveis.

Atenuações

Essa alteração afeta apenas os usuários que regularmente refazem o scaffold de seu banco de dados em um modelo de código EF ("fluxo de banco de dados primeiro").

É recomendável reagir a essa alteração modificando seu código para usar a propriedade bool não anulável. No entanto, se isso não for possível, você pode editar os modelos de andaimes para reverter para o mapeamento anterior. Para fazer isso, configure os modelos como descrito nesta página . Em seguida, edite o arquivo EntityType.t4, localize onde as propriedades da entidade são geradas (pesquise property.ClrType) e altere o código para o seguinte:

#>
        var propertyClrType = property.ClrType != typeof(bool)
                              || (property.GetDefaultValueSql() == null && property.GetDefaultValue() != null)
            ? property.ClrType
            : typeof(bool?);
#>
    public <#= code.Reference(propertyClrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#
<#

Alterações de baixo impacto

Os métodos SQLite Math agora se traduzem para SQL

Tracking Issue #18843

Comportamento antigo

Anteriormente, apenas os métodos Abs, Max, Min e Round em Math eram traduzidos para SQL. Todos os outros membros seriam avaliados no cliente se aparecessem na expressão Select final de uma consulta.

Novo comportamento

No EF Core 8.0, todos os métodos Math com funções matemáticas SQLite correspondentes são traduzidos para SQL.

Essas funções matemáticas foram habilitadas na biblioteca SQLite nativa que fornecemos por padrão (por meio de nossa dependência do pacote NuGet SQLitePCLRaw.bundle_e_sqlite3). Eles também foram habilitados na biblioteca fornecida pela SQLitePCLRaw.bundle_e_sqlcipher. Se você estiver usando uma dessas bibliotecas, seu aplicativo não deve ser afetado por essa alteração.

Há uma chance, no entanto, de que os aplicativos, incluindo a biblioteca SQLite nativa por outros meios, podem não habilitar as funções matemáticas. Nesses casos, os métodos Math serão traduzidos para SQL e encontrarão nenhuma função erros quando executados.

Porquê

SQLite adicionou funções matemáticas incorporadas na versão 3.35.0. Apesar de estarem desativados por padrão, eles se tornaram generalizados o suficiente para que decidimos fornecer traduções padrão para eles em nosso provedor EF Core SQLite.

Também colaboramos com Eric Sink no projeto SQLitePCLRaw para habilitar funções matemáticas em todas as bibliotecas SQLite nativas fornecidas como parte desse projeto.

Atenuações

A maneira mais simples de corrigir quebras é, quando possível, habilitar a função matemática na biblioteca SQLite nativa especificando a opção de compilação SQLITE_ENABLE_MATH_FUNCTIONS.

Se você não controlar a compilação da biblioteca nativa, também poderá corrigir quebras criando as funções por conta própria em tempo de execução usando as APIs de Microsoft.Data.Sqlite .

sqliteConnection
    .CreateFunction<double, double, double>(
        "pow",
        Math.Pow,
        isDeterministic: true);

Como alternativa, você pode forçar a avaliação do cliente dividindo a expressão Select em duas partes separadas por AsEnumerable.

// Before
var query = dbContext.Cylinders
    .Select(
        c => new
        {
            Id = c.Id
            // May throw "no such function: pow"
            Volume = Math.PI * Math.Pow(c.Radius, 2) * c.Height
        });

// After
var query = dbContext.Cylinders
    // Select the properties you'll need from the database
    .Select(
        c => new
        {
            c.Id,
            c.Radius,
            c.Height
        })
    // Switch to client-eval
    .AsEnumerable()
    // Select the final results
    .Select(
        c => new
        {
            Id = c.Id,
            Volume = Math.PI * Math.Pow(c.Radius, 2) * c.Height
        });

ITypeBase substitui IEntityType em algumas APIs

Questão de Rastreamento #13947

Comportamento antigo

Anteriormente, todos os tipos estruturais mapeados eram tipos de entidade.

Novo comportamento

Com a introdução de tipos complexos no EF8, algumas APIs que antes usavam um IEntityType agora usam ITypeBase para que as APIs possam ser usadas com entidades ou tipos complexos. Isto inclui:

  • IProperty.DeclaringEntityType está agora obsoleta e IProperty.DeclaringType deve ser utilizada em vez disso.
  • IEntityTypeIgnoredConvention está agora obsoleta e ITypeIgnoredConvention deve ser utilizada em vez disso.
  • IValueGeneratorSelector.Select agora aceita um ITypeBase que pode ser, mas não precisa ser um IEntityType.

Porquê

Com a introdução de tipos complexos no EF8, essas APIs podem ser usadas com IEntityType ou IComplexType.

Atenuações

As APIs antigas estão obsoletas, mas não serão removidas até o EF10. O código deve ser atualizado para usar as novas APIs o mais rápido possível.

As expressões ValueConverter e ValueComparer devem usar APIs públicas para o modelo compilado

Tracking Issue #24896

Comportamento antigo

Anteriormente, ValueConverter e ValueComparer definições não eram incluídas no modelo compilado e, portanto, podiam conter código arbitrário.

Novo comportamento

O EF agora extrai as expressões dos objetos ValueConverter e ValueComparer e inclui esses C# no modelo compilado. Isso significa que essas expressões devem usar apenas a API pública.

Porquê

A equipe do EF está gradualmente movendo mais construções para o modelo compilado para suportar o uso do EF Core com AOT no futuro.

Atenuações

Torne públicas as APIs usadas pelo comparador. Por exemplo, considere este conversor simples:

public class MyValueConverter : ValueConverter<string, byte[]>
{
    public MyValueConverter()
        : base(v => ConvertToBytes(v), v => ConvertToString(v))
    {
    }

    private static string ConvertToString(byte[] bytes)
        => ""; // ... TODO: Conversion code

    private static byte[] ConvertToBytes(string chars)
        => Array.Empty<byte>(); // ... TODO: Conversion code
}

Para usar este conversor em um modelo compilado com EF8, os métodos ConvertToString e ConvertToBytes devem ser tornados públicos. Por exemplo:

public class MyValueConverter : ValueConverter<string, byte[]>
{
    public MyValueConverter()
        : base(v => ConvertToBytes(v), v => ConvertToString(v))
    {
    }

    public static string ConvertToString(byte[] bytes)
        => ""; // ... TODO: Conversion code

    public static byte[] ConvertToBytes(string chars)
        => Array.Empty<byte>(); // ... TODO: Conversion code
}

ExcludeFromMigrations não exclui mais outras tabelas em uma hierarquia TPC

Tracking Issue #30079

Comportamento antigo

Anteriormente, o uso de ExcludeFromMigrations em uma tabela em uma hierarquia TPC também excluía outras tabelas na hierarquia.

Novo comportamento

A partir do EF Core 8.0, ExcludeFromMigrations não afeta outras tabelas.

Porquê

O comportamento antigo era um bug e impedia que as migrações fossem usadas para gerenciar hierarquias entre projetos.

Atenuações

Use ExcludeFromMigrations explicitamente em qualquer outra tabela que deva ser excluída.

Chaves de inteiros não sombreadas são persistidas em documentos do Cosmos

Problema de rastreamento #31664

Comportamento antigo

Anteriormente, as propriedades inteiras não sombreadas que correspondem aos critérios para ser uma propriedade de chave sintetizada não eram persistidas no documento JSON, mas eram ressintetizadas na saída.

Novo comportamento

A partir do EF Core 8.0, essas propriedades agora persistem.

Porquê

O comportamento antigo era um bug e impedia que propriedades que correspondessem aos critérios-chave sintetizados fossem persistidas no Cosmos.

Atenuações

Exclua a propriedade do modelo se seu valor não deve ser persistente. Além disso, pode-se desabilitar esse comportamento inteiramente ao definir a alternância AppContext Microsoft.EntityFrameworkCore.Issue31664 como true. Consulte AppContext para utilizadores de bibliotecas para mais detalhes.

AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue31664", isEnabled: true);

O modelo relacional é gerado no modelo compilado

Tracking Issue #24896

Comportamento antigo

Anteriormente, o modelo relacional era calculado em tempo de execução, mesmo quando se usava um modelo compilado.

Novo comportamento

A partir do EF Core 8.0, o modelo relacional faz parte do modelo compilado gerado. No entanto, para modelos particularmente grandes, o arquivo gerado pode falhar ao compilar.

Porquê

Isso foi feito para melhorar ainda mais o tempo de inicialização.

Atenuações

Edite o arquivo de *ModelBuilder.cs gerado e remova a linha AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel());, bem como o método CreateRelationalModel().

Os andaimes podem gerar nomes de navegação diferentes

Tracking Issue #27832

Comportamento antigo

Anteriormente, ao estruturar um DbContext e tipos de entidade de um banco de dados existente, os nomes de navegação para relacionamentos às vezes eram derivados de um prefixo comum de vários nomes de coluna de chave estrangeira.

Novo comportamento

A partir do EF Core 8.0, prefixos comuns de nomes de colunas de uma chave estrangeira composta não são mais usados para gerar nomes de navegação.

Porquê

Esta é uma regra de nomenclatura obscura que às vezes gera nomes muito pobres como, S, Student_, ou mesmo apenas _. Sem essa regra, nomes estranhos não são mais gerados, e as convenções de nomenclatura para navegações também são simplificadas, tornando mais fácil entender e prever quais nomes serão gerados.

Atenuações

As EF Core Power Tools têm a opção de continuar a gerar navegações no modo antigo. Como alternativa, o código gerado pode ser totalmente personalizado usando modelos T4 . Isso pode ser usado para exemplificar as propriedades de chave estrangeira em relações de estruturação e aplicar qualquer regra apropriada para o seu código, a fim de gerar os nomes de navegação necessários.

Os discriminadores têm agora um comprimento máximo

Tracking Issue #10691

Comportamento antigo

Anteriormente, as colunas discriminadoras criadas para o mapeamento de herança TPH eram configuradas como nvarchar(max) no SQL Server/Azure SQL, ou como o tipo de cadeia de caracteres sem limite equivalente em outros bancos de dados.

Novo comportamento

A partir do EF Core 8.0, as colunas do discriminador são criadas com um comprimento máximo que abrange todos os valores de discriminador conhecidos. O EF gerará uma migração para fazer essa alteração. No entanto, se a coluna discriminadora for restringida de alguma forma - por exemplo, como parte de um índice - a AlterColumn criada por Migrações poderá falhar.

Porquê

nvarchar(max) colunas são ineficientes e desnecessárias quando os comprimentos de todos os valores possíveis são conhecidos.

Atenuações

O tamanho da coluna pode ser explicitamente ilimitado:

modelBuilder.Entity<Foo>()
    .Property<string>("Discriminator")
    .HasMaxLength(-1);

Os valores de chave do SQL Server são comparados sem distinção entre maiúsculas e minúsculas

Problema de rastreamento #27526

Comportamento antigo

Anteriormente, ao controlar entidades com chaves de cadeia de caracteres com os provedores de banco de dados SQL do SQL Server/Azure, os valores de chave eram comparados usando o comparador ordinal padrão que diferencia maiúsculas de minúsculas do .NET.

Novo comportamento

A partir do EF Core 8.0, os valores de chave de cadeia de caracteres SQL do SQL Server/Azure são comparados usando o comparador ordinal padrão que não diferencia maiúsculas de minúsculas do .NET.

Porquê

Por padrão, o SQL Server utiliza comparações que não diferenciam maiúsculas de minúsculas ao comparar valores de chave estrangeira com valores correspondentes de chave principal. Isso significa que, quando o EF usa comparações que diferenciam maiúsculas de minúsculas, ele pode não conectar uma chave estrangeira a uma chave principal quando deveria.

Atenuações

As comparações que diferenciam maiúsculas de minúsculas podem ser usadas definindo um ValueComparerpersonalizado. Por exemplo:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var comparer = new ValueComparer<string>(
        (l, r) => string.Equals(l, r, StringComparison.Ordinal),
        v => v.GetHashCode(),
        v => v);

    modelBuilder.Entity<Blog>()
        .Property(e => e.Id)
        .Metadata.SetValueComparer(comparer);

    modelBuilder.Entity<Post>(
        b =>
        {
            b.Property(e => e.Id).Metadata.SetValueComparer(comparer);
            b.Property(e => e.BlogId).Metadata.SetValueComparer(comparer);
        });
}

Várias chamadas AddDbContext são aplicadas em ordem diferente

Tracking Issue #32518

Comportamento antigo

Anteriormente, quando várias chamadas para AddDbContext, AddDbContextPool, AddDbContextFactory ou AddPooledDbContextFactor eram feitas com o mesmo tipo de contexto, mas configuração conflitante, a primeira venceu.

Novo comportamento

A partir do EF Core 8.0, a configuração da última invocação terá precedência.

Porquê

Isso foi alterado para ser consistente com o novo método ConfigureDbContext que pode ser usado para adicionar configuração antes ou depois dos métodos Add*.

Atenuações

Inverta a ordem das chamadas Add*.

EntityTypeAttributeConventionBase substituído por TypeAttributeConventionBase

Novo comportamento

No EF Core 8.0, EntityTypeAttributeConventionBase foi renomeado para TypeAttributeConventionBase.

Porquê

TypeAttributeConventionBase representa melhor a funcionalidade, pois agora pode ser usado para tipos complexos e tipos de entidade.

Atenuações

Substitua as utilizações de EntityTypeAttributeConventionBase por TypeAttributeConventionBase.