Memutus perubahan di EF Core 8 (EF8)
Halaman ini mendokuensikan PERUBAHAN API dan perilaku yang berpotensi memutus pembaruan aplikasi yang ada dari EF Core 7 ke EF Core 8. Pastikan untuk meninjau perubahan yang melanggar sebelumnya jika memperbarui dari versi EF Core yang lebih lama:
EF Core 8 menargetkan .NET 8. Aplikasi yang menargetkan versi .NET, .NET Core, dan .NET Framework yang lebih lama perlu diperbarui untuk menargetkan .NET 8.
EF memiliki dukungan khusus untuk kueri LINQ menggunakan operator Contains
melalui daftar nilai berparameter:
var names = new[] { "Blog1", "Blog2" };
var blogs = await context.Blogs
.Where(b => names.Contains(b.Name))
.ToArrayAsync();
Sebelum EF Core 8.0, EF menyisipkan nilai berparameter sebagai konstanta ke dalam SQL:
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2')
Dimulai dengan EF Core 8.0, EF sekarang menghasilkan SQL yang lebih efisien dalam banyak kasus, tetapi tidak didukung pada SQL Server 2014 dan di bawah ini:
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]
)
Perhatikan bahwa versi SQL Server yang lebih baru dapat dikonfigurasi dengan tingkat kompatibilitas yang lebih lama, juga membuatnya tidak kompatibel dengan SQL baru. Ini juga dapat terjadi dengan database Azure SQL yang dimigrasikan dari instans SQL Server lokal sebelumnya, membawa tingkat kompatibilitas lama.
Penyisipan nilai konstanta ke dalam SQL menciptakan banyak masalah performa, mengganggu penyimpanan rencana kueri dan menyebabkan penghapusan kueri lain yang tidak perlu. Terjemahan EF Core 8.0 baru menggunakan fungsi SQL Server OPENJSON
untuk mentransfer nilai sebagai array JSON. Ini memecahkan masalah performa yang melekat pada teknik sebelumnya; namun, OPENJSON
fungsi ini tidak tersedia di SQL Server 2014 ke bawah.
Untuk informasi selengkapnya tentang perubahan ini, lihat posting blog ini.
Jika database Anda adalah SQL Server 2016 (13.x) atau yang lebih baru, atau jika Anda menggunakan Azure SQL, periksa tingkat kompatibilitas database Anda yang dikonfigurasi melalui perintah berikut:
SELECT name, compatibility_level FROM sys.databases;
Jika tingkat kompatibilitas di bawah 130 (SQL Server 2016), pertimbangkan untuk memodifikasinya ke nilai yang lebih baru (dokumentasi).
Jika tidak, jika versi database Anda benar-benar lebih lama dari SQL Server 2016, atau diatur ke tingkat kompatibilitas lama yang tidak dapat Anda ubah karena alasan tertentu, Anda dapat mengonfigurasi EF untuk kembali ke SQL pra-8.0 yang lebih lama. Jika Anda menggunakan EF 9, Anda dapat menggunakan TranslateParameterizedCollectionsToConstantsyang baru diperkenalkan:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer("<CONNECTION STRING>", o => o.TranslateParameterizedCollectionsToConstants())
Jika Anda menggunakan EF 8, Anda dapat mencapai efek yang sama saat menggunakan SQL Server dengan mengonfigurasi tingkat kompatibilitas SQL EF:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"<CONNECTION STRING>", o => o.UseCompatibilityLevel(120));
EF memiliki dukungan khusus untuk kueri LINQ menggunakan operator Contains
melalui daftar nilai berparameter:
var names = new[] { "Blog1", "Blog2" };
var blogs = await context.Blogs
.Where(b => names.Contains(b.Name))
.ToArrayAsync();
Sebelum EF Core 8.0, EF menyisipkan nilai berparameter sebagai konstanta ke dalam SQL:
SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2')
Dimulai dengan EF Core 8.0, EF sekarang menghasilkan yang berikut:
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]
)
Namun, setelah rilis EF 8, ternyata meskipun SQL baru lebih efisien untuk sebagian besar kasus, dalam sebagian kecil kasus, performanya bisa jauh kurang efisien, bahkan menyebabkan time-out kueri dalam beberapa kasus.
Silakan lihat komentar ini untuk ringkasan perubahan di EF 8, mitigasi parsial yang disediakan di EF 9, dan rencana ke depan untuk EF 10.
Jika Anda menggunakan EF 9, Anda dapat menggunakan TranslateParameterizedCollectionsToConstants yang baru diperkenalkan untuk mengembalikan terjemahan Contains
untuk semua kueri kembali ke perilaku pra-8.0:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer("<CONNECTION STRING>", o => o.TranslateParameterizedCollectionsToConstants())
Jika Anda menggunakan EF 8, Anda dapat mencapai efek yang sama saat menggunakan SQL Server dengan mengonfigurasi tingkat kompatibilitas SQL EF:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(@"<CONNECTION STRING>", o => o.UseCompatibilityLevel(120));
Terakhir, Anda dapat mengontrol terjemahan berdasarkan kueri demi kueri menggunakan EF.Constant sebagai berikut:
var blogs = await context.Blogs
.Where(b => EF.Constant(names).Contains(b.Name))
.ToArrayAsync();
Dalam EF7, enum yang dipetakan ke JSON adalah, secara default, disimpan sebagai nilai string dalam dokumen JSON.
Dimulai dengan EF Core 8.0, EF sekarang, secara default, memetakan enum ke nilai bilangan bulat dalam dokumen JSON.
EF selalu, secara default, enum yang dipetakan ke kolom numerik dalam database relasional. Karena EF mendukung kueri di mana nilai dari JSON berinteraksi dengan nilai dari kolom dan parameter, penting bahwa nilai dalam JSON cocok dengan nilai di kolom non-JSON.
Untuk terus menggunakan string, konfigurasikan properti enum dengan konversi. Contohnya:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().Property(e => e.Status).HasConversion<string>();
}
Atau, untuk semua properti jenis enum::
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Properties<StatusEnum>().HaveConversion<string>();
}
Sebelumnya, saat membuat perancah database SQL Server dengan date
atau time
kolom, EF akan menghasilkan properti entitas dengan jenis DateTime dan TimeSpan.
Dimulai dengan EF Core 8.0, date
dan time
di-scaffold sebagai DateOnly dan TimeOnly.
DateOnly dan TimeOnly diperkenalkan dalam .NET 6.0, dan sangat cocok untuk memetakan jenis tanggal dan waktu database.
DateTime terutama berisi komponen waktu yang tidak digunakan dan dapat menyebabkan kebingungan saat memetakannya ke date
, dan TimeSpan mewakili interval waktu - mungkin termasuk hari - daripada waktu hari di mana suatu peristiwa terjadi. Menggunakan jenis baru mencegah bug dan kebingungan, dan memberikan kejelasan niat.
Perubahan ini hanya memengaruhi pengguna yang secara teratur menyusun ulang database mereka ke dalam model kode EF (alur "database-first").
Disarankan untuk bereaksi terhadap perubahan ini dengan memodifikasi kode Anda untuk menggunakan perancah DateOnly dan TimeOnly jenis yang baru. Namun, jika itu tidak memungkinkan, Anda dapat mengedit templat perancah untuk kembali ke pemetaan sebelumnya. Untuk melakukan ini, siapkan templat seperti yang dijelaskan di halaman ini. Kemudian, edit EntityType.t4
file, temukan di mana properti entitas dibuat (cari property.ClrType
), dan ubah kode menjadi berikut:
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!;" : "" #>
<#
Sebelumnya, kolom yang tidak dapat bool
diubah ke null dengan batasan default database dibuat sebagai properti nullable bool?
.
Dimulai dengan EF Core 8.0, kolom yang tidak dapat bool
diubah ke null selalu diacak sebagai properti yang tidak dapat diubah ke null.
Properti bool
tidak akan memiliki nilainya yang dikirim ke database jika nilai tersebut adalah false
, yang merupakan default CLR. Jika database memiliki nilai true
default untuk kolom , maka meskipun nilai properti adalah false
, nilai dalam database berakhir sebagai true
. Namun, di EF8, sentinel yang digunakan untuk menentukan apakah properti memiliki nilai dapat diubah. Ini dilakukan secara otomatis untuk bool
properti dengan nilai yang dihasilkan database , true
yang berarti bahwa tidak lagi diperlukan untuk membuat perancah properti sebagai nullable.
Perubahan ini hanya memengaruhi pengguna yang secara teratur menyusun ulang database mereka ke dalam model kode EF (alur "database-first").
Disarankan untuk bereaksi terhadap perubahan ini dengan memodifikasi kode Anda untuk menggunakan properti bool yang tidak dapat diubah ke null. Namun, jika itu tidak memungkinkan, Anda dapat mengedit templat perancah untuk kembali ke pemetaan sebelumnya. Untuk melakukan ini, siapkan templat seperti yang dijelaskan di halaman ini. Kemudian, edit EntityType.t4
file, temukan di mana properti entitas dibuat (cari property.ClrType
), dan ubah kode menjadi berikut:
#>
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!;" : "" #>
<#
<#
Sebelumnya hanya metode Abs, Max, Min, dan Round yang diterjemahkan Math
ke SQL. Semua anggota lain akan dievaluasi pada klien jika mereka muncul di ekspresi Pilih kueri akhir.
Dalam EF Core 8.0, semua Math
metode dengan fungsi matematika SQLite yang sesuai diterjemahkan ke SQL.
Fungsi matematika ini telah diaktifkan di pustaka SQLite asli yang kami sediakan secara default (melalui dependensi kami pada paket nuGet SQLitePCLRaw.bundle_e_sqlite3). Mereka juga telah diaktifkan di pustaka yang disediakan oleh SQLitePCLRaw.bundle_e_sqlcipher. Jika Anda menggunakan salah satu pustaka ini, aplikasi Anda tidak boleh terpengaruh oleh perubahan ini.
Namun, ada kemungkinan bahwa aplikasi termasuk pustaka SQLite asli dengan cara lain mungkin tidak mengaktifkan fungsi matematika. Dalam kasus ini, Math
metode akan diterjemahkan ke SQL dan tidak mengalami kesalahan fungsi seperti itu saat dijalankan.
SQLite menambahkan fungsi matematika bawaan dalam versi 3.35.0. Meskipun dinonaktifkan secara default, mereka telah menjadi cukup pervasif sehingga kami memutuskan untuk memberikan terjemahan default untuk mereka di penyedia EF Core SQLite kami.
Kami juga berkolaborasi dengan Eric Sink pada proyek SQLitePCLRaw untuk mengaktifkan fungsi matematika di semua pustaka SQLite asli yang disediakan sebagai bagian dari proyek tersebut.
Cara paling sederhana untuk memperbaiki jeda adalah, jika memungkinkan, untuk mengaktifkan fungsi matematika adalah pustaka SQLite asli dengan menentukan opsi waktu kompilasi SQLITE_ENABLE_MATH_FUNCTIONS .
Jika Anda tidak mengontrol kompilasi pustaka asli, Anda juga dapat memperbaiki jeda dengan membuat fungsi sendiri saat runtime menggunakan API Microsoft.Data.Sqlite .
sqliteConnection
.CreateFunction<double, double, double>(
"pow",
Math.Pow,
isDeterministic: true);
Atau, Anda dapat memaksa evaluasi klien dengan membagi ekspresi Pilih menjadi dua bagian yang dipisahkan oleh 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
});
Sebelumnya, semua jenis struktural yang dipetakan adalah jenis entitas.
Dengan pengenalan jenis kompleks di EF8, beberapa API yang sebelumnya menggunakan IEntityType
sekarang ITypeBase
sehingga API dapat digunakan dengan jenis entitas atau kompleks. Drive ini termasuk:
-
IProperty.DeclaringEntityType
sekarang usang danIProperty.DeclaringType
harus digunakan sebagai gantinya. -
IEntityTypeIgnoredConvention
sekarang usang danITypeIgnoredConvention
harus digunakan sebagai gantinya. -
IValueGeneratorSelector.Select
sekarang menerima yangITypeBase
mungkin, tetapi tidak harus menjadi .IEntityType
Dengan pengenalan jenis kompleks di EF8, API ini dapat digunakan dengan IEntityType
atau IComplexType
.
API lama usang, tetapi tidak akan dihapus hingga EF10. Kode harus diperbarui untuk menggunakan API baru ASAP.
Sebelumnya, ValueConverter
ValueComparer
dan definisi tidak disertakan dalam model yang dikompilasi, sehingga dapat berisi kode arbitrer.
EF sekarang mengekstrak ekspresi dari ValueConverter
objek dan ValueComparer
dan menyertakan C# ini dalam model yang dikompilasi. Ini berarti bahwa ekspresi ini hanya boleh menggunakan API publik.
Tim EF secara bertahap memindahkan lebih banyak konstruksi ke dalam model yang dikompilasi untuk mendukung penggunaan EF Core dengan AOT di masa depan.
Buat API yang digunakan oleh perbandingan publik. Misalnya, pertimbangkan pengonversi sederhana ini:
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
}
Untuk menggunakan pengonversi ini dalam model yang dikompilasi dengan EF8, ConvertToString
metode dan ConvertToBytes
harus dibuat publik. Contohnya:
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
}
Sebelumnya, menggunakan ExcludeFromMigrations
pada tabel dalam hierarki TPC juga akan mengecualikan tabel lain dalam hierarki.
Dimulai dengan EF Core 8.0, ExcludeFromMigrations
tidak berdampak pada tabel lain.
Perilaku lama adalah bug dan mencegah migrasi digunakan untuk mengelola hierarki di seluruh proyek.
Gunakan ExcludeFromMigrations
secara eksplisit pada tabel lain yang harus dikecualikan.
Sebelumnya, properti bilangan bulat non-bayangan yang cocok dengan kriteria untuk menjadi properti kunci yang disintesis tidak akan disimpan ke dalam dokumen JSON, tetapi sebaliknya disintesis ulang saat keluar.
Dimulai dengan EF Core 8.0, properti ini sekarang bertahan.
Perilaku lama adalah bug dan mencegah properti yang cocok dengan kriteria kunci yang disintesis agar tidak dipertahankan ke Cosmos.
Kecualikan properti dari model jika nilainya tidak boleh dipertahankan.
Selain itu, Anda dapat menonaktifkan perilaku ini sepenuhnya dengan mengatur Microsoft.EntityFrameworkCore.Issue31664
appContext beralih ke true
, lihat AppContext untuk konsumen pustaka untuk detail selengkapnya.
AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue31664", isEnabled: true);
Sebelumnya, model relasional dihitung pada run-time bahkan saat menggunakan model yang dikompilasi.
Dimulai dengan EF Core 8.0, model relasional adalah bagian dari model yang dikompilasi yang dihasilkan. Namun, untuk model yang sangat besar, file yang dihasilkan mungkin gagal dikompilasi.
Hal ini dilakukan untuk lebih meningkatkan waktu startup.
Edit file yang dihasilkan *ModelBuilder.cs
dan hapus baris AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel());
serta metode CreateRelationalModel()
.
Sebelumnya ketika perancah DbContext
jenis entitas dan dari database yang ada, nama navigasi untuk hubungan terkadang berasal dari awalan umum dari beberapa nama kolom kunci asing.
Dimulai dengan EF Core 8.0, awalan umum nama kolom dari kunci asing komposit tidak lagi digunakan untuk menghasilkan nama navigasi.
Ini adalah aturan penamaan yang tidak jelas yang kadang-kadang menghasilkan nama yang sangat buruk seperti, , S
Student_
, atau bahkan hanya _
. Tanpa aturan ini, nama-nama aneh tidak lagi dihasilkan, dan konvensi penamaan untuk navigasi juga dibuat lebih sederhana, sehingga membuatnya lebih mudah untuk memahami dan memprediksi nama mana yang akan dihasilkan.
EF Core Power Tools memiliki opsi untuk terus menghasilkan navigasi dengan cara lama. Atau, kode yang dihasilkan dapat sepenuhnya disesuaikan menggunakan templat T4. Ini dapat digunakan untuk contoh properti kunci asing dari hubungan perancah dan menggunakan aturan apa pun yang sesuai untuk kode Anda untuk menghasilkan nama navigasi yang Anda butuhkan.
Sebelumnya, kolom diskriminator yang dibuat untuk pemetaan warisan TPH dikonfigurasi seperti nvarchar(max)
pada SQL Server/Azure SQL, atau jenis string tidak terbatas yang setara pada database lain.
Dimulai dengan EF Core 8.0, kolom diskriminator dibuat dengan panjang maksimum yang mencakup semua nilai diskriminator yang diketahui. EF akan menghasilkan migrasi untuk melakukan perubahan ini. Namun, jika kolom diskriminator dibatasi dalam beberapa cara -- misalnya, sebagai bagian dari indeks -- maka AlterColumn
yang dibuat oleh Migrasi mungkin gagal.
nvarchar(max)
kolom tidak efisien dan tidak perlu ketika panjang semua nilai yang mungkin diketahui.
Ukuran kolom dapat dibuat secara eksplisit tidak terbatas:
modelBuilder.Entity<Foo>()
.Property<string>("Discriminator")
.HasMaxLength(-1);
Sebelumnya, saat melacak entitas dengan kunci string dengan penyedia database SQL Server/Azure SQL, nilai kunci dibandingkan menggunakan perbandingan ordinal peka huruf besar/kecil .NET default.
Dimulai dengan EF Core 8.0, nilai kunci string SQL Server/Azure SQL dibandingkan menggunakan perbandingan ordinal yang tidak peka huruf besar/kecil .NET default.
Secara default, SQL Server menggunakan perbandingan yang tidak peka huruf besar/kecil saat membandingkan nilai kunci asing untuk kecocokan dengan nilai kunci utama. Ini berarti ketika EF menggunakan perbandingan peka huruf besar/kecil, EF mungkin tidak menghubungkan kunci asing ke kunci utama kapan seharusnya.
Perbandingan peka huruf besar/kecil dapat digunakan dengan mengatur kustom ValueComparer
. Contohnya:
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);
});
}
Sebelumnya, ketika beberapa panggilan ke AddDbContext
, AddDbContextPool
, AddDbContextFactory
atau AddPooledDbContextFactor
dilakukan dengan jenis konteks yang sama tetapi konfigurasi yang bertentangan, yang pertama menang.
Dimulai dengan EF Core 8.0, konfigurasi dari panggilan terakhir akan diutamakan.
Ini diubah agar konsisten dengan metode ConfigureDbContext
baru yang dapat digunakan untuk menambahkan konfigurasi baik sebelum atau sesudah Add*
metode.
Membalikkan urutan Add*
panggilan.
Dalam EF Core 8.0 EntityTypeAttributeConventionBase
diganti namanya menjadi TypeAttributeConventionBase
.
TypeAttributeConventionBase
mewakili fungsionalitas dengan lebih baik karena sekarang dapat digunakan untuk jenis kompleks dan jenis entitas.
Ganti EntityTypeAttributeConventionBase
penggunaan dengan TypeAttributeConventionBase
.
Umpan balik .NET
.NET adalah proyek sumber terbuka. Pilih tautan untuk memberikan umpan balik: