Icke-bakåtkompatibla ändringar i EF Core 8 (EF8)
På den här sidan dokumenteras API-ändringar och beteendeändringar som kan bryta befintliga program som uppdateras från EF Core 7 till EF Core 8. Se till att gå igenom tidigare större ändringar om du uppdaterar från en tidigare version av EF Core.
Målramverk
EF Core 8 riktar in sig på .NET 8. Program som riktar sig till äldre .NET-, .NET Core- och .NET Framework-versioner måste uppdateras till målet .NET 8.
Sammanfattning
Ändringar med hög påverkan
Contains
i LINQ-frågor kan sluta fungera i äldre SQL Server-versioner
Gammalt beteende
EF hade specialiserat stöd för LINQ-frågor med Contains
operatorn över en parameteriserad värdelista:
var names = new[] { "Blog1", "Blog2" };
var blogs = await context.Blogs
.Where(b => names.Contains(b.Name))
.ToArrayAsync();
Före EF Core 8.0 infogade EF de parameteriserade värdena som konstanter i SQL:
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2')
Nytt beteende
Från och med EF Core 8.0 genererar EF nu SQL som är mer effektivt i många fall, men som inte stöds på SQL Server 2014 och nedan:
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]
)
Observera att nyare SQL Server-versioner kan konfigureras med en äldre kompatibilitetsnivå, vilket också gör dem inkompatibla med den nya SQL-koden. Detta kan också inträffa med en Azure SQL-databas som migrerades från en tidigare lokal SQL Server-instans och som överförde den gamla kompatibilitetsnivån.
Varför
Införandet av konstanta värden i SQL skapar många prestandaproblem, vilket motverkar cachelagring av frågeplaner och orsakar onödiga borttagningar av andra frågor. Den nya EF Core 8.0-översättningen använder funktionen SQL Server OPENJSON
för att i stället överföra värdena som en JSON-matris. Detta löser prestandaproblemen i den tidigare tekniken. Funktionen OPENJSON
är dock inte tillgänglig i SQL Server 2014 eller senare.
Mer information om den här ändringen finns i det här blogginlägget
Åtgärder
Om databasen är SQL Server 2016 (13.x) eller senare, eller om du använder Azure SQL, kontrollerar du databasens konfigurerade kompatibilitetsnivå via följande kommando:
SELECT name, compatibility_level FROM sys.databases;
Om kompatibilitetsnivån är under 130 (SQL Server 2016) bör du överväga att ändra den till ett nyare värde (dokumentation).
Om databasversionen inte är äldre än SQL Server 2016, eller om den är inställd på en gammal kompatibilitetsnivå som du inte kan ändra av någon anledning, kan du konfigurera EF för att återgå till den äldre, före 8.0 SQL. Om du använder EF 9 kan du använda den nyligen introducerade TranslateParameterizedCollectionsToConstants:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer("<CONNECTION STRING>", o => o.TranslateParameterizedCollectionsToConstants())
Om du använder EF 8 kan du uppnå samma effekt när du använder SQL Server genom att konfigurera EF:s SQL-kompatibilitetsnivå:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"<CONNECTION STRING>", o => o.UseCompatibilityLevel(120));
Möjliga frågeprestandaregressioner runt Contains
i LINQ-frågor
Gammalt beteende
EF hade specialiserat stöd för LINQ-frågor med Contains
operatorn över en parameteriserad värdelista:
var names = new[] { "Blog1", "Blog2" };
var blogs = await context.Blogs
.Where(b => names.Contains(b.Name))
.ToArrayAsync();
Före EF Core 8.0 infogade EF de parameteriserade värdena som konstanter i SQL:
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2')
Nytt beteende
Från och med EF Core 8.0 genererar EF nu följande:
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]
)
Men efter lanseringen av EF 8 visade det sig att även om den nya SQL:n är effektivare i de flesta fall kan den vara dramatiskt mindre effektiv i en minoritet av fallen, även om den orsakar tidsgränser för frågor i vissa fall.
Se den här kommentaren för en sammanfattning av ändringen i EF 8, de partiella minskningarna som anges i EF 9 och planen för EF 10.
Åtgärder
Om du använder EF 9 kan du använda den nyligen introducerade TranslateParameterizedCollectionsToConstants för att återställa Contains
översättning för alla frågor tillbaka till beteendet före 8.0:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer("<CONNECTION STRING>", o => o.TranslateParameterizedCollectionsToConstants())
Om du använder EF 8 kan du uppnå samma effekt när du använder SQL Server genom att konfigurera EF:s SQL-kompatibilitetsnivå:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"<CONNECTION STRING>", o => o.UseCompatibilityLevel(120));
Slutligen kan du styra översättningen efter fråga med hjälp av EF.Constant på följande sätt:
var blogs = await context.Blogs
.Where(b => EF.Constant(names).Contains(b.Name))
.ToArrayAsync();
Uppräkningar i JSON lagras som heltal i stället för strängar som standardinställning.
Gammalt beteende
I EF7 lagras enumerationer som mappas till JSON som standard som strängvärden i JSON-dokumentet.
Nytt beteende
Från och med EF Core 8.0 mappar EF nu som standard uppräkningar till heltalsvärden i JSON-dokumentet.
Varför
EF har alltid som standard mappat uppräkningar till en numerisk kolumn i relationsdatabaser. Eftersom EF stöder frågor där värden från JSON interagerar med värden från kolumner och parametrar är det viktigt att värdena i JSON matchar värdena i kolumnen som inte är JSON.
Åtgärder
Om du vill fortsätta använda strängar konfigurerar du uppräkningsegenskapen med en konvertering. Till exempel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Property(e => e.Status).HasConversion<string>();
}
Eller för alla egenskaper av uppräkningstypen::
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Properties<StatusEnum>().HaveConversion<string>();
}
Ändringar med medelpåverkan
Nu skapar SQL Server date
och time
mallar till .NET DateOnly
och TimeOnly
Gammalt beteende
Tidigare, när du skapar en SQL Server-databas med date
eller time
kolumner, skulle EF generera entitetsegenskaper med typer DateTime och TimeSpan.
Nytt beteende
Från och med EF Core 8.0 genereras date
och time
som DateOnly och TimeOnly.
Varför
DateOnly och TimeOnly introducerades i .NET 6.0 och är en perfekt matchning för att mappa databasens datum- och tidstyper.
DateTime innehåller särskilt en tidskomponent som inte används och kan orsaka förvirring när den mappas till date
, och TimeSpan representerar ett tidsintervall – eventuellt inklusive dagar – i stället för en tid på dagen då en händelse inträffar. Att använda de nya typerna förhindrar buggar och förvirring och ger klarhet i avsikten.
Åtgärder
Den här ändringen påverkar endast användare som regelbundet omskapar sin databas till en EF-kodmodell ("databas-först"-flöde).
Vi rekommenderar att du reagerar på den här ändringen genom att ändra koden så att den använder de nyligen kodade DateOnly- och TimeOnly typerna. Om det däremot inte är möjligt kan du redigera mallarna för strukturen för att gå tillbaka till den tidigare mappningen. Det gör du genom att konfigurera mallarna enligt beskrivningen på den här sidan. Redigera sedan filen EntityType.t4
, leta reda på var entitetsegenskaperna genereras (sök efter property.ClrType
) och ändra koden till följande:
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!;" : "" #>
<#
Booleska kolumner med ett databasgenererat värde genereras inte längre som nullable.
Gammalt beteende
Tidigare har icke-nullbara bool
kolumner med en databasstandardbegränsning kodats som nullbara bool?
egenskaper.
Nytt beteende
Från och med EF Core 8.0 genereras icke-nullbara bool
-kolumnerna alltid som icke-nullbara egenskaperna.
Varför
En bool
-egenskap får inte sitt värde skickat till databasen om värdet är false
, vilket är CLR-standardvärdet. Om databasen har standardvärdet true
för kolumnen, hamnar värdet i databasen som false
även om värdet för egenskapen är true
. Dock kan sentineln, som i EF8 används för att avgöra om en egenskap har ett värde, ändras. Detta görs automatiskt för bool
-egenskaper med ett databasgenererat värde på true
, vilket innebär att det inte längre är nödvändigt att generera egenskaperna som nullbara.
Åtgärder
Den här ändringen påverkar endast användare som regelbundet omskapar sin databas till en EF-kodmodell ("databas-först"-flöde).
Vi rekommenderar att du reagerar på den här ändringen genom att ändra koden så att den använder egenskapen bool som inte kan nulleras. Om det däremot inte är möjligt kan du redigera mallarna för strukturen för att gå tillbaka till den tidigare mappningen. Det gör du genom att konfigurera mallarna enligt beskrivningen på den här sidan. Redigera sedan filen EntityType.t4
, leta reda på var entitetsegenskaperna genereras (sök efter property.ClrType
) och ändra koden till följande:
#>
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!;" : "" #>
<#
<#
Ändringar med låg påverkan
SQLite-Math
metoder översätts nu till SQL
Gammalt beteende
Tidigare översattes endast metoderna Abs, Max, Min och Round på Math
till SQL. Alla andra medlemmar skulle utvärderas på klientsidan om de visas i det slutliga Select-uttrycket för en sökfråga.
Nytt beteende
I EF Core 8.0 översätts alla Math
metoder med motsvarande SQLite-matematiska funktioner till SQL.
Dessa matematiska funktioner har aktiverats i det interna SQLite-biblioteket som vi tillhandahåller som standard (genom vårt beroende av SQLitePCLRaw.bundle_e_sqlite3 NuGet-paketet). De har också aktiverats i biblioteket som tillhandahålls av SQLitePCLRaw.bundle_e_sqlcipher. Om du använder något av dessa bibliotek bör ditt program inte påverkas av den här ändringen.
Det finns dock en chans att program inklusive det interna SQLite-biblioteket på andra sätt kanske inte aktiverar matematiska funktioner. I dessa fall översätts Math
-metoderna till SQL och påträffar ingen sådan funktion fel när de körs.
Varför
SQLite har lagt till inbyggda matematiska funktioner i version 3.35.0. Även om de är inaktiverade som standard har de blivit tillräckligt genomgripande för att vi ska kunna tillhandahålla standardöversättningar för dem i vår EF Core SQLite-provider.
Vi samarbetade också med Eric Sink i projektet SQLitePCLRaw för att aktivera matematiska funktioner i alla interna SQLite-bibliotek som ingår i projektet.
Åtgärder
Det enklaste sättet att åtgärda avbrott är, när det är möjligt, att aktivera matematikfunktionerna i det inbyggda SQLite-biblioteket genom att ange alternativet SQLITE_ENABLE_MATH_FUNCTIONS vid kompileringstidspunkt.
Om du inte styr kompilering av det interna biblioteket kan du även åtgärda pauser genom att skapa funktionerna själv vid körning med hjälp av api:erna Microsoft.Data.Sqlite.
sqliteConnection
.CreateFunction<double, double, double>(
"pow",
Math.Pow,
isDeterministic: true);
Du kan också tvinga fram klientutvärdering genom att dela upp Select-uttrycket i två delar avgränsade med 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 ersätter IEntityType i vissa API:er
Gammalt beteende
Tidigare var alla mappade strukturella typer entitetstyper.
Nytt beteende
Med introduktionen av komplexa typer i EF8 använder vissa API:er som tidigare använt en IEntityType
nu ITypeBase
så att API:erna kan användas med antingen entitetstyper eller komplexa typer. Detta inkluderar:
-
IProperty.DeclaringEntityType
är nu föråldrad ochIProperty.DeclaringType
bör användas i stället. -
IEntityTypeIgnoredConvention
är nu föråldrad ochITypeIgnoredConvention
bör användas i stället. -
IValueGeneratorSelector.Select
accepterar nu enITypeBase
som kan vara, men som inte behöver vara enIEntityType
.
Varför
Med introduktionen av komplexa typer i EF8 kan dessa API:er användas med antingen IEntityType
eller IComplexType
.
Åtgärder
De gamla API:erna är föråldrade, men tas inte bort förrän EF10. Koden bör uppdateras för att använda de nya API:erna ASAP.
ValueConverter- och ValueComparer-uttryck måste använda offentliga API:er för den kompilerade modellen
Gammalt beteende
Tidigare inkluderades inte ValueConverter
och ValueComparer
definitioner i den kompilerade modellen och kan därför innehålla godtycklig kod.
Nytt beteende
EF extraherar nu uttrycken från ValueConverter
- och ValueComparer
-objekten och inkluderar dessa C# i den kompilerade modellen. Det innebär att dessa uttryck endast får använda offentligt API.
Varför
EF-teamet flyttar gradvis fler konstruktioner till den kompilerade modellen för att stödja användning av EF Core med AOT i framtiden.
Åtgärder
Gör API:erna som används av jämförelsen offentliga. Tänk dig till exempel den här enkla konverteraren:
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
}
Om du vill använda konverteraren i en kompilerad modell med EF8 måste metoderna ConvertToString
och ConvertToBytes
offentliggöras. Till exempel:
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 utesluter inte längre andra tabeller i en TPC-hierarki
Gammalt beteende
Tidigare skulle användning av ExcludeFromMigrations
i en tabell i en TPC-hierarki också utesluta andra tabeller i hierarkin.
Nytt beteende
Från och med EF Core 8.0 påverkar ExcludeFromMigrations
inte andra tabeller.
Varför
Det gamla beteendet var en bugg och förhindrade migreringar från att användas för att hantera hierarkier mellan projekt.
Åtgärder
Använd ExcludeFromMigrations
explicit i andra tabeller som ska undantas.
Icke-skugg heltalsnycklar sparas i Cosmos-dokument
Gammalt beteende
Tidigare skulle icke-skugg heltalsegenskaper som matchar kriterierna för att vara en syntetiserad nyckelegenskap inte bevaras i JSON-dokumentet, utan syntetiserades i stället på nytt på vägen ut.
Nytt beteende
Från och med EF Core 8.0 sparas dessa egenskaper nu.
Varför
Det gamla beteendet var en bugg och förhindrade att egenskaper som matchar de syntetiserade nyckelkriterierna bevarades till Cosmos.
Åtgärder
Undanta egenskapen från modellen om dess värde inte ska sparas.
Dessutom kan du inaktivera det här beteendet helt genom att ange Microsoft.EntityFrameworkCore.Issue31664
AppContext växla till true
, se AppContext för bibliotekskonsumenter mer information.
AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue31664", isEnabled: true);
Relationsmodellen genereras i den kompilerade modellen
Gammalt beteende
Tidigare beräknades relationsmodellen vid körning även när en kompilerad modell användes.
Nytt beteende
Från och med EF Core 8.0 är relationsmodellen en del av den genererade kompilerade modellen. För särskilt stora modeller kan dock den genererade filen inte kompileras.
Varför
Detta gjordes för att ytterligare förbättra starttiden.
Åtgärder
Redigera den genererade *ModelBuilder.cs
-filen och ta bort raden AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel());
samt metoden CreateRelationalModel()
.
Ramverk kan generera olika navigeringsnamn
Gammalt beteende
Tidigare när man skapade en DbContext
och entitetstyper från en befintlig databas kom navigeringsnamnen för relationer ibland från ett gemensamt prefix av flera främmande nyckelkolumnnamn.
Nytt beteende
Från och med EF Core 8.0 används inte längre vanliga prefix för kolumnnamn från en sammansatt sekundärnyckel för att generera navigeringsnamn.
Varför
Det här är en obskyr namngivningsregel som ibland genererar mycket dåliga namn som S
, Student_
eller till och med bara _
. Utan den här regeln genereras inte längre konstiga namn, och namngivningskonventionerna för navigering blir också enklare, vilket gör det lättare att förstå och förutsäga vilka namn som ska genereras.
Åtgärder
EF Core Power Tools har möjlighet att fortsätta att generera navigeringar på det gamla sättet. Du kan också anpassa koden helt med hjälp av T4-mallar. Detta kan användas för att exemplifiera de främmande nyckelegenskaperna i stödjande relationer och tillämpa den regel som är lämplig för din kod för att generera de navigeringsnamn du behöver.
Diskriminatorer har nu en maxlängd
Gammalt beteende
Tidigare konfigurerades diskriminatorkolumner skapade för TPH-arvsmappning som nvarchar(max)
på SQL Server/Azure SQL, eller som motsvarande obundna strängtyp på andra databaser.
Nytt beteende
Från och med EF Core 8.0 skapas diskriminerande kolumner med en maximal längd som täcker alla kända diskriminerande värden. EF genererar en migrering för att göra denna ändring. Men om diskriminator-kolumnen är begränsad på något sätt – till exempel som en del av ett index – kan AlterColumn
, som skapas av Migrations, misslyckas.
Varför
nvarchar(max)
kolumner är ineffektiva och onödiga när längden på alla möjliga värden är kända.
Åtgärder
Kolumnstorleken kan göras explicit obunden.
modelBuilder.Entity<Foo>()
.Property<string>("Discriminator")
.HasMaxLength(-1);
SQL Server-nyckelvärden jämförs skiftlägesokänsligt
Gammalt beteende
Tidigare, när du spårade entiteter med strängnycklar i SQL Server/Azure SQL-databasprovidrar, jämfördes nyckelvärdena med den skiftlägeskänsliga ordningskomparatorn i .NET som standard.
Nytt beteende
Från och med EF Core 8.0 jämförs SQL Server/Azure SQL-strängnyckelvärden med den skiftlägesokänsliga ordningsjämföraren som är standard i .NET.
Varför
Som standard använder SQL Server skiftlägesokänsliga jämförelser när du jämför sekundärnyckelvärden för matchningar med huvudnyckelvärden. Det betyder att när EF använder skiftlägeskänsliga jämförelser kanske det inte ansluter en främmande nyckel till en huvudnyckel när det borde.
Åtgärder
Skiftlägeskänsliga jämförelser kan användas genom att ställa in en anpassad inställning för ValueComparer
. Till exempel:
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);
});
}
Flera AddDbContext-anrop tillämpas i olika ordning
Gammalt beteende
Tidigare, när flera anrop gjordes till AddDbContext
, AddDbContextPool
, AddDbContextFactory
eller AddPooledDbContextFactor
med samma kontexttyp men med motstridiga konfigurationer, var det den första som vann.
Nytt beteende
Från och med EF Core 8.0 har konfigurationen från det senaste anropet företräde.
Varför
Detta ändrades så att det överensstämmer med den nya metoden ConfigureDbContext
som kan användas för att lägga till konfiguration antingen före eller efter Add*
-metoderna.
Åtgärder
Ändra ordningen på Add*
-anrop.
EntityTypeAttributeConventionBase ersatt med TypeAttributeConventionBase
Nytt beteende
I EF Core 8.0 bytte EntityTypeAttributeConventionBase
namn till TypeAttributeConventionBase
.
Varför
TypeAttributeConventionBase
representerar funktionen bättre eftersom den nu kan användas för komplexa typer och entitetstyper.
Åtgärder
Ersätt EntityTypeAttributeConventionBase
med TypeAttributeConventionBase
.