Zmiany powodujące niezgodność w programie EF Core 8 (EF8)
Ta strona dokumentuje zmiany interfejsu API i zachowania, które mogą przerwać istniejące aplikacje aktualizowane z programu EF Core 7 do EF Core 8. Pamiętaj, aby przejrzeć wcześniejsze zmiany powodujące niezgodność w przypadku aktualizacji z wcześniejszej wersji programu EF Core:
Struktura docelowa
Program EF Core 8 jest przeznaczony dla platformy .NET 8. Aplikacje przeznaczone dla starszych wersji platform .NET, .NET Core i .NET Framework będą musiały być aktualizowane tak, aby dotyczyły platformy .NET 8.
Podsumowanie
Zmiany o dużym wpływie
Contains
w zapytaniach LINQ może przestać działać w starszych wersjach programu SQL Server
Problem ze śledzeniem nr 13617
Stare zachowanie
Wcześniej, gdy Contains
operator był używany w zapytaniach LINQ z listą wartości sparametryzowanych, program EF wygenerował program SQL, który był nieefektywny, ale pracował nad wszystkimi wersjami programu SQL Server.
Nowe zachowanie
Począwszy od programu EF Core 8.0, platforma EF generuje teraz program SQL, który jest bardziej wydajny, ale nie jest obsługiwany w programie SQL Server 2014 i poniżej.
Należy pamiętać, że nowsze wersje programu SQL Server można skonfigurować ze starszym poziomem zgodności, co również sprawia, że są one niezgodne z nowym programem SQL. Może to również wystąpić w przypadku bazy danych Azure SQL Database, która została zmigrowana z poprzedniego lokalnego wystąpienia programu SQL Server, przenosząc stary poziom zgodności.
Dlaczego
Poprzedni kod SQL wygenerowany przez program EF Core dla Contains
wstawił sparametryzowane wartości jako stałe w języku SQL. Na przykład następujące zapytanie LINQ:
var names = new[] { "Blog1", "Blog2" };
var blogs = await context.Blogs
.Where(b => names.Contains(b.Name))
.ToArrayAsync();
... zostanie przetłumaczony na następujący kod SQL:
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2')
Takie wstawianie wartości stałych do bazy danych SQL powoduje wiele problemów z wydajnością, pokonując buforowanie planu zapytań i powodując niepotrzebne eksmisji innych zapytań. Nowe tłumaczenie programu EF Core 8.0 używa funkcji PROGRAMU SQL Server OPENJSON
, aby zamiast tego przenieść wartości jako tablicę JSON. Rozwiązuje to problemy z wydajnością związane z poprzednią techniką; OPENJSON
jednak funkcja jest niedostępna w programie SQL Server 2014 i nowszym.
Aby uzyskać więcej informacji na temat tej zmiany, zobacz ten wpis w blogu.
Środki zaradcze
Jeśli baza danych to SQL Server 2016 (13.x) lub nowsza, lub jeśli używasz usługi Azure SQL, sprawdź skonfigurowany poziom zgodności bazy danych za pomocą następującego polecenia:
SELECT name, compatibility_level FROM sys.databases;
Jeśli poziom zgodności jest niższy niż 130 (SQL Server 2016), rozważ zmodyfikowanie go do nowszej wartości (dokumentacja).
W przeciwnym razie, jeśli wersja bazy danych jest naprawdę starsza niż SQL Server 2016 lub jest ustawiona na stary poziom zgodności, którego nie można zmienić z jakiegoś powodu, skonfiguruj program EF Core, aby przywrócić starszy, mniej wydajny program SQL w następujący sposób:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"<CONNECTION STRING>", o => o.UseCompatibilityLevel(120));
Wyliczenia w formacie JSON są przechowywane jako liczby ints zamiast ciągów domyślnie
Problem ze śledzeniem nr 13617
Stare zachowanie
W programie EF7 wyliczenia mapowane na format JSON są domyślnie przechowywane jako wartości ciągu w dokumencie JSON.
Nowe zachowanie
Począwszy od programu EF Core 8.0, ef teraz domyślnie mapuje wyliczenia na wartości całkowite w dokumencie JSON.
Dlaczego
Program EF zawsze mapował wyliczenia na kolumnę liczbową w relacyjnych bazach danych. Ponieważ program EF obsługuje zapytania, w których wartości z formatu JSON współdziałają z wartościami z kolumn i parametrów, ważne jest, aby wartości w formacie JSON odpowiadały wartościom w kolumnie innej niż JSON.
Środki zaradcze
Aby kontynuować korzystanie z ciągów, skonfiguruj właściwość wyliczenia za pomocą konwersji. Na przykład:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Property(e => e.Status).HasConversion<string>();
}
Lub dla wszystkich właściwości typu wyliczenia::
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Properties<StatusEnum>().HaveConversion<string>();
}
Zmiany o średnim wpływie
Program SQL Server date
i time
teraz szkielet na platformie .NET DateOnly
i TimeOnly
Problem ze śledzeniem nr 24507
Stare zachowanie
Wcześniej podczas tworzenia szkieletu bazy danych programu SQL Server z kolumnami date
lub time
program EF wygenerował właściwości jednostki z typami DateTime i TimeSpan.
Nowe zachowanie
Począwszy od programu EF Core 8.0 i date
time
są szkieletowe jako DateOnly i TimeOnly.
Dlaczego
DateOnly i TimeOnly zostały wprowadzone na platformie .NET 6.0 i są idealnym dopasowaniem do mapowania typów daty i godziny bazy danych. DateTime w szczególności zawiera składnik czasu, który przechodzi nieużywane i może powodować zamieszanie podczas mapowania go na date
, i TimeSpan reprezentuje interwał czasu — ewentualnie w tym dni — a nie godzinę, w której występuje zdarzenie. Użycie nowych typów zapobiega błędom i nieporozumieniu oraz zapewnia przejrzystość intencji.
Środki zaradcze
Ta zmiana dotyczy tylko użytkowników, którzy regularnie ponownie tworzyją szkielet bazy danych w modelu kodu EF ("database-first" flow).
Zaleca się zareagowanie na tę zmianę przez zmodyfikowanie kodu w celu użycia nowo utworzonych DateOnly szkieletów i TimeOnly typów. Jeśli jednak nie jest to możliwe, możesz edytować szablony tworzenia szkieletów, aby przywrócić poprzednie mapowanie. W tym celu skonfiguruj szablony zgodnie z opisem na tej stronie. Następnie zmodyfikuj EntityType.t4
plik, znajdź miejsce wygenerowania właściwości jednostki (wyszukaj property.ClrType
ciąg ), a następnie zmień kod na następujący:
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!;" : "" #>
<#
Kolumny logiczne z wygenerowaną wartością bazy danych nie są już szkieletowe jako dopuszczane do wartości null
Problem ze śledzeniem nr 15070
Stare zachowanie
Wcześniej kolumny bez wartości null bool
z domyślnym ograniczeniem bazy danych były szkieletowe jako właściwości dopuszczane bool?
do wartości null.
Nowe zachowanie
Począwszy od programu EF Core 8.0, kolumny bez wartości null bool
są zawsze szkieletowe jako właściwości niepuste.
Dlaczego
Właściwość bool
nie będzie mieć jej wartości wysyłanej do bazy danych, jeśli ta wartość to false
, która jest wartością domyślną CLR. Jeśli baza danych ma wartość true
domyślną dla kolumny, mimo że wartość właściwości to false
, wartość w bazie danych kończy się jako true
. Jednak w programie EF8 sentinel używany do określenia, czy właściwość ma wartość, można zmienić. Jest to wykonywane automatycznie dla bool
właściwości z wygenerowaną wartością true
bazy danych , co oznacza, że nie jest już konieczne utworzenie szkieletu właściwości jako dopuszczających wartość null.
Środki zaradcze
Ta zmiana dotyczy tylko użytkowników, którzy regularnie ponownie tworzyją szkielet bazy danych w modelu kodu EF ("database-first" flow).
Zaleca się zareagowanie na tę zmianę przez zmodyfikowanie kodu tak, aby używał właściwości logicznej, która nie może mieć wartości null. Jeśli jednak nie jest to możliwe, możesz edytować szablony tworzenia szkieletów, aby przywrócić poprzednie mapowanie. W tym celu skonfiguruj szablony zgodnie z opisem na tej stronie. Następnie zmodyfikuj EntityType.t4
plik, znajdź miejsce wygenerowania właściwości jednostki (wyszukaj property.ClrType
ciąg ), a następnie zmień kod na następujący:
#>
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!;" : "" #>
<#
<#
Zmiany o niskim wpływie
Metody SQLite Math
teraz tłumaczą się na język SQL
Problem ze śledzeniem nr 18843
Poprzednie działanie
Wcześniej tylko metody Math
Abs, Max, Min i Round zostały przetłumaczone na język SQL. Wszyscy inni członkowie będą oceniani na kliencie, jeśli pojawi się w końcowym wyrażeniu Select zapytania.
Nowe zachowanie
W programie EF Core 8.0 wszystkie Math
metody z odpowiednimi funkcjami matematycznymi SQLite są tłumaczone na język SQL.
Te funkcje matematyczne zostały włączone w natywnej bibliotece SQLite, którą udostępniamy domyślnie (za pośrednictwem naszej zależności od pakietu SQLitePCLRaw.bundle_e_sqlite3 NuGet). Zostały one również włączone w bibliotece udostępnionej przez SQLitePCLRaw.bundle_e_sqlcipher. Jeśli używasz jednej z tych bibliotek, ta zmiana nie powinna mieć wpływu na aplikację.
Istnieje jednak szansa, że aplikacje, w tym natywna biblioteka SQLite, mogą nie włączać funkcji matematycznych. W takich przypadkach Math
metody zostaną przetłumaczone na język SQL i nie napotkają takich błędów funkcji podczas wykonywania.
Dlaczego
SqLite dodał wbudowane funkcje matematyczne w wersji 3.35.0. Mimo że są one domyślnie wyłączone, stały się one wystarczająco wszechobecne, że postanowiliśmy udostępnić domyślne tłumaczenia dla nich w naszym dostawcy EF Core SQLite.
Współpracujemy również z Eric Sink w projekcie SQLitePCLRaw, aby umożliwić funkcje matematyczne we wszystkich natywnych bibliotekach SQLite udostępnianych w ramach tego projektu.
Środki zaradcze
Najprostszym sposobem naprawienia podziałów jest włączenie funkcji matematycznej jest natywna biblioteka SQLite, określając opcję SQLITE_ENABLE_MATH_FUNCTIONS czasu kompilacji.
Jeśli nie kontrolujesz kompilacji biblioteki natywnej, możesz również naprawić przerwy, tworząc funkcje samodzielnie w czasie wykonywania przy użyciu interfejsów API Microsoft.Data.Sqlite .
sqliteConnection
.CreateFunction<double, double, double>(
"pow",
Math.Pow,
isDeterministic: true);
Alternatywnie możesz wymusić ocenę klienta, dzieląc wyrażenie Select na dwie części oddzielone ciągami 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 zastępuje typ IEntityType w niektórych interfejsach API
Problem ze śledzeniem nr 13947
Stare zachowanie
Wcześniej wszystkie mapowane typy strukturalne były typami jednostek.
Nowe zachowanie
Wraz z wprowadzeniem złożonych typów w programie EF8 niektóre interfejsy API, które były wcześniej używaneITypeBase
, używają IEntityType
teraz, aby interfejsy API mogły być używane z jednostkami lub typami złożonymi. Obejmuje to:
IProperty.DeclaringEntityType
jest teraz przestarzałe iIProperty.DeclaringType
należy go użyć.IEntityTypeIgnoredConvention
jest teraz przestarzałe iITypeIgnoredConvention
należy go użyć.IValueGeneratorSelector.Select
teraz akceptuje elementITypeBase
, który może być, ale nie musi być .IEntityType
Dlaczego
Wraz z wprowadzeniem złożonych typów w programie EF8 te interfejsy API mogą być używane z IEntityType
programem lub IComplexType
.
Środki zaradcze
Stare interfejsy API są przestarzałe, ale nie zostaną usunięte do czasu ef10. Kod powinien zostać zaktualizowany w celu korzystania z nowych interfejsów API ASAP.
Wyrażenia ValueConverter i ValueComparer muszą używać publicznych interfejsów API dla skompilowanego modelu
Problem ze śledzeniem nr 24896
Stare zachowanie
ValueConverter
Wcześniej definicje i ValueComparer
nie zostały uwzględnione w skompilowanym modelu, więc mogą zawierać dowolny kod.
Nowe zachowanie
Program EF wyodrębnia teraz wyrażenia z ValueConverter
obiektów i ValueComparer
i i uwzględnia je w skompilowanym modelu w języku C#. Oznacza to, że te wyrażenia muszą używać tylko publicznego interfejsu API.
Dlaczego
Zespół EF stopniowo przenosi kolejne konstrukcje do skompilowanego modelu w celu obsługi korzystania z platformy EF Core z funkcją AOT w przyszłości.
Środki zaradcze
Ustaw interfejsy API używane przez moduł porównujący jako publiczne. Rozważmy na przykład ten prosty konwerter:
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
}
Aby użyć tego konwertera w skompilowanym modelu z platformą EF8, ConvertToString
metody i ConvertToBytes
muszą być upublicznione. Na przykład:
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 nie wyklucza już innych tabel w hierarchii TPC
Stare zachowanie
Wcześniej użycie w ExcludeFromMigrations
tabeli w hierarchii TPC wykluczało również inne tabele w hierarchii.
Nowe zachowanie
Począwszy od programu EF Core 8.0, ExcludeFromMigrations
nie ma wpływu na inne tabele.
Dlaczego
Stare zachowanie było usterką i uniemożliwiło migracje używane do zarządzania hierarchiami między projektami.
Środki zaradcze
Użyj ExcludeFromMigrations
jawnie dowolnej innej tabeli, która powinna zostać wykluczona.
Klucze całkowite bez cienia są utrwalane w dokumentach usługi Cosmos
Problem ze śledzeniem nr 31664
Stare zachowanie
Wcześniej właściwości liczb całkowitych innych niż w tle, które spełniają kryteria syntetyzowanej właściwości klucza, nie zostaną utrwalone w dokumencie JSON, ale zamiast tego zostały ponownie zsyntetyzowane w drodze.
Nowe zachowanie
Począwszy od programu EF Core 8.0, te właściwości są teraz utrwalane.
Dlaczego
Stare zachowanie było usterką i uniemożliwiało utrwalanie właściwości pasujących do syntetyzowanych kluczowych kryteriów do usługi Cosmos.
Środki zaradcze
Wyklucz właściwość z modelu , jeśli jej wartość nie powinna być utrwalana.
Ponadto możesz całkowicie wyłączyć to zachowanie, ustawiając Microsoft.EntityFrameworkCore.Issue31664
przełącznik AppContext na true
, zobacz AppContext dla użytkowników biblioteki, aby uzyskać więcej szczegółów.
AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue31664", isEnabled: true);
Model relacyjny jest generowany w skompilowanym modelu
Problem ze śledzeniem nr 24896
Stare zachowanie
Wcześniej model relacyjny został obliczony w czasie wykonywania nawet w przypadku korzystania z skompilowanego modelu.
Nowe zachowanie
Począwszy od programu EF Core 8.0, model relacyjny jest częścią wygenerowanego skompilowanego modelu. Jednak w przypadku szczególnie dużych modeli wygenerowany plik może nie zostać skompilowany.
Dlaczego
Zostało to zrobione, aby jeszcze bardziej poprawić czas uruchamiania.
Środki zaradcze
Edytuj wygenerowany *ModelBuilder.cs
plik i usuń wiersz AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel());
, a także metodę CreateRelationalModel()
.
Tworzenie szkieletów może generować różne nazwy nawigacji
Problem ze śledzeniem nr 27832
Stare zachowanie
Wcześniej podczas tworzenia DbContext
szkieletu typów jednostek i z istniejącej bazy danych nazwy nawigacji relacji były czasami pochodzące z wspólnego prefiksu wielu nazw kolumn kluczy obcych.
Nowe zachowanie
Począwszy od programu EF Core 8.0, typowe prefiksy nazw kolumn z złożonego klucza obcego nie są już używane do generowania nazw nawigacji.
Dlaczego
Jest to niejasna reguła nazewnictwa, która czasami generuje bardzo słabe nazwy, takie jak , S
, Student_
lub nawet tylko _
. Bez tej reguły dziwne nazwy nie są już generowane, a konwencje nazewnictwa nawigacji są również prostsze, co ułatwia zrozumienie i przewidywanie, które nazwy zostaną wygenerowane.
Środki zaradcze
Narzędzia EF Core Power Tools mają możliwość utrzymywania generowania nawigacji w stary sposób. Alternatywnie wygenerowany kod można w pełni dostosować przy użyciu szablonów T4. Może to służyć do przykładu właściwości klucza obcego relacji tworzenia szkieletu i używania dowolnej reguły właściwej dla kodu w celu wygenerowania potrzebnych nazw nawigacji.
Dyskryminujące mają teraz maksymalną długość
Stare zachowanie
Wcześniej kolumny dyskryminujące utworzone na potrzeby mapowania dziedziczenia TPH zostały skonfigurowane tak jak nvarchar(max)
w programie SQL Server/Azure SQL lub równoważnym niezwiązanym typie ciągu w innych bazach danych.
Nowe zachowanie
Począwszy od programu EF Core 8.0, kolumny dyskryminujące są tworzone z maksymalną długością obejmującą wszystkie znane wartości dyskryminujące. Program EF wygeneruje migrację, aby wprowadzić tę zmianę. Jeśli jednak kolumna dyskryminująca jest ograniczona w jakiś sposób — na przykład w ramach indeksu — AlterColumn
tworzenie przez migracje może zakończyć się niepowodzeniem.
Dlaczego
nvarchar(max)
kolumny są nieefektywne i niepotrzebne, gdy są znane długości wszystkich możliwych wartości.
Środki zaradcze
Rozmiar kolumny można jawnie cofnąć:
modelBuilder.Entity<Foo>()
.Property<string>("Discriminator")
.HasMaxLength(-1);
Wartości kluczy programu SQL Server są porównywane bez uwzględniania wielkości liter
Problem ze śledzeniem nr 27526
Stare zachowanie
Wcześniej podczas śledzenia jednostek przy użyciu kluczy ciągów u dostawców usług SQL Server/Azure SQL Database wartości kluczy były porównywane przy użyciu domyślnego porównania porządkowego uwzględniającego wielkość liter platformy .NET.
Nowe zachowanie
Począwszy od programu EF Core 8.0, wartości klucza ciągu SQL Server/Azure SQL są porównywane przy użyciu domyślnego modułu porównywania wielkości liter platformy .NET bez uwzględniania wielkości liter.
Dlaczego
Domyślnie program SQL Server używa porównań bez uwzględniania wielkości liter podczas porównywania wartości klucza obcego dla dopasowań do wartości klucza głównego. Oznacza to, że gdy program EF używa porównań z uwzględnieniem wielkości liter, może nie łączyć klucza obcego z kluczem głównym, gdy powinien.
Środki zaradcze
Porównania z uwzględnieniem wielkości liter mogą być używane przez ustawienie niestandardowego ValueComparer
. Na przykład:
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);
});
}
Wiele wywołań AddDbContext jest stosowanych w innej kolejności
Problem ze śledzeniem nr 32518
Stare zachowanie
Wcześniej, gdy wiele wywołań metody AddDbContext
, AddDbContextPool
AddDbContextFactory
lub AddPooledDbContextFactor
zostało wykonanych z tym samym typem kontekstu, ale konfiguracja powodująca konflikt, pierwsza wygrała.
Nowe zachowanie
Począwszy od programu EF Core 8.0, konfiguracja z ostatniego wywołania będzie mieć pierwszeństwo.
Dlaczego
Ta zmiana została zmieniona tak, aby była zgodna z nową metodą ConfigureDbContext
, która może służyć do dodawania konfiguracji przed metodami lub po nich Add*
.
Środki zaradcze
Odwraca kolejność wywołań Add*
.
Metoda EntityTypeAttributeConventionBase została zastąpiona typeAttributeConventionBase
Nowe zachowanie
W programie EF Core 8.0 EntityTypeAttributeConventionBase
zmieniono nazwę na TypeAttributeConventionBase
.
Dlaczego
TypeAttributeConventionBase
reprezentuje funkcjonalność lepszą, ponieważ teraz może być używana dla typów złożonych i typów jednostek.
Środki zaradcze
Zastąp EntityTypeAttributeConventionBase
wartości użycia ciągiem TypeAttributeConventionBase
.