Bagikan melalui


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:

Kerangka Target

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.

Ringkasan

Perubahan mencolok Dampak
Contains dalam kueri LINQ mungkin berhenti bekerja pada versi SQL Server yang lebih lama Sangat Penting
Enum di JSON disimpan sebagai ints alih-alih string secara default Sangat Penting
SQL Server date dan time sekarang perancah ke .NET DateOnly dan TimeOnly Medium
Kolom Boolean dengan nilai yang dihasilkan database tidak lagi di-scaffolded sebagai nullable Medium
Metode SQLite Math sekarang diterjemahkan ke SQL Kurang Penting
ITypeBase menggantikan IEntityType di beberapa API Kurang Penting
Ekspresi ValueGenerator harus menggunakan API publik Kurang Penting
ExcludeFromMigrations tidak lagi mengecualikan tabel lain dalam hierarki TPC Kurang Penting
Kunci bilangan bulat non-bayangan dipertahankan ke dokumen Cosmos Kurang Penting
Model relasional dihasilkan dalam model yang dikompilasi Kurang Penting
Perancah dapat menghasilkan nama navigasi yang berbeda Kurang Penting
Diskriminator sekarang memiliki panjang maksimum Kurang Penting
Nilai kunci SQL Server dibandingkan secara tidak peka huruf besar/kecil Kurang Penting

Perubahan berdampak tinggi

Contains dalam kueri LINQ mungkin berhenti bekerja pada versi SQL Server yang lebih lama

Masalah Pelacakan #13617

Perilaku yang lama

Sebelumnya, ketika Contains operator digunakan dalam kueri LINQ dengan daftar nilai berparameter, EF menghasilkan SQL yang tidak efisien tetapi bekerja pada semua versi SQL Server.

Perilaku yang baru

Dimulai dengan EF Core 8.0, EF sekarang menghasilkan SQL yang lebih efisien, tetapi tidak didukung pada SQL Server 2014 ke bawah.

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.

Mengapa

SQL sebelumnya yang dihasilkan oleh EF Core untuk Contains menyisipkan nilai berparameter sebagai konstanta di SQL. Misalnya, kueri LINQ berikut:

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

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

... akan diterjemahkan ke SQL berikut:

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

Penyisipan nilai konstanta tersebut ke dalam SQL menciptakan banyak masalah performa, mengalahkan penembolokan rencana kueri dan menyebabkan pengeluaran kueri lain yang tidak diperlukan. 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.

Mitigasi

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 beberapa alasan, konfigurasikan EF Core untuk kembali ke SQL yang lebih lama dan kurang efisien sebagai berikut:

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

Enum di JSON disimpan sebagai ints alih-alih string secara default

Masalah Pelacakan #13617

Perilaku yang lama

Dalam EF7, enum yang dipetakan ke JSON adalah, secara default, disimpan sebagai nilai string dalam dokumen JSON.

Perilaku yang baru

Dimulai dengan EF Core 8.0, EF sekarang, secara default, memetakan enum ke nilai bilangan bulat dalam dokumen JSON.

Mengapa

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.

Mitigasi

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>();
}

Perubahan dampak sedang

SQL Server date dan time sekarang perancah ke .NET DateOnly dan TimeOnly

Masalah Pelacakan #24507

Perilaku yang lama

Sebelumnya, saat membuat perancah database SQL Server dengan date atau time kolom, EF akan menghasilkan properti entitas dengan jenis DateTime dan TimeSpan.

Perilaku yang baru

Dimulai dengan EF Core 8.0, date dan time di-scaffold sebagai DateOnly dan TimeOnly.

Mengapa

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.

Mitigasi

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!;" : "" #>
<#

Kolom Boolean dengan nilai yang dihasilkan database tidak lagi di-scaffolded sebagai nullable

Masalah Pelacakan #15070

Perilaku yang lama

Sebelumnya, kolom yang tidak dapat bool diubah ke null dengan batasan default database dibuat sebagai properti nullable bool? .

Perilaku yang baru

Dimulai dengan EF Core 8.0, kolom yang tidak dapat bool diubah ke null selalu diacak sebagai properti yang tidak dapat diubah ke null.

Mengapa

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 , trueyang berarti bahwa tidak lagi diperlukan untuk membuat perancah properti sebagai nullable.

Mitigasi

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!;" : "" #>
<#
<#

Perubahan berdampak rendah

Metode SQLite Math sekarang diterjemahkan ke SQL

Masalah Pelacakan #18843

Perilaku Lama

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.

Perilaku yang baru

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.

Mengapa

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.

Mitigasi

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
        });

ITypeBase menggantikan IEntityType di beberapa API

Masalah Pelacakan #13947

Perilaku yang lama

Sebelumnya, semua jenis struktural yang dipetakan adalah jenis entitas.

Perilaku yang baru

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 dan IProperty.DeclaringType harus digunakan sebagai gantinya.
  • IEntityTypeIgnoredConvention sekarang usang dan ITypeIgnoredConvention harus digunakan sebagai gantinya.
  • IValueGeneratorSelector.Selectsekarang menerima yang ITypeBase mungkin, tetapi tidak harus menjadi .IEntityType

Mengapa

Dengan pengenalan jenis kompleks di EF8, API ini dapat digunakan dengan IEntityType atau IComplexType.

Mitigasi

API lama usang, tetapi tidak akan dihapus hingga EF10. Kode harus diperbarui untuk menggunakan API baru ASAP.

Ekspresi ValueConverter dan ValueComparer harus menggunakan API publik untuk model yang dikompilasi

Masalah Pelacakan #24896

Perilaku yang lama

Sebelumnya, ValueConverter ValueComparer dan definisi tidak disertakan dalam model yang dikompilasi, sehingga dapat berisi kode arbitrer.

Perilaku yang baru

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.

Mengapa

Tim EF secara bertahap memindahkan lebih banyak konstruksi ke dalam model yang dikompilasi untuk mendukung penggunaan EF Core dengan AOT di masa depan.

Mitigasi

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
}

ExcludeFromMigrations tidak lagi mengecualikan tabel lain dalam hierarki TPC

Masalah Pelacakan #30079

Perilaku yang lama

Sebelumnya, menggunakan ExcludeFromMigrations pada tabel dalam hierarki TPC juga akan mengecualikan tabel lain dalam hierarki.

Perilaku yang baru

Dimulai dengan EF Core 8.0, ExcludeFromMigrations tidak berdampak pada tabel lain.

Mengapa

Perilaku lama adalah bug dan mencegah migrasi digunakan untuk mengelola hierarki di seluruh proyek.

Mitigasi

Gunakan ExcludeFromMigrations secara eksplisit pada tabel lain yang harus dikecualikan.

Kunci bilangan bulat non-bayangan dipertahankan ke dokumen Cosmos

Masalah Pelacakan #31664

Perilaku yang lama

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.

Perilaku yang baru

Dimulai dengan EF Core 8.0, properti ini sekarang bertahan.

Mengapa

Perilaku lama adalah bug dan mencegah properti yang cocok dengan kriteria kunci yang disintesis agar tidak dipertahankan ke Cosmos.

Mitigasi

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);

Model relasional dihasilkan dalam model yang dikompilasi

Masalah Pelacakan #24896

Perilaku yang lama

Sebelumnya, model relasional dihitung pada run-time bahkan saat menggunakan model yang dikompilasi.

Perilaku yang baru

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.

Mengapa

Hal ini dilakukan untuk lebih meningkatkan waktu startup.

Mitigasi

Edit file yang dihasilkan *ModelBuilder.cs dan hapus baris AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel()); serta metode CreateRelationalModel().

Perancah dapat menghasilkan nama navigasi yang berbeda

Masalah Pelacakan #27832

Perilaku yang lama

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.

Perilaku yang baru

Dimulai dengan EF Core 8.0, awalan umum nama kolom dari kunci asing komposit tidak lagi digunakan untuk menghasilkan nama navigasi.

Mengapa

Ini adalah aturan penamaan yang tidak jelas yang kadang-kadang menghasilkan nama yang sangat buruk seperti, , SStudent_, 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.

Mitigasi

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.

Diskriminator sekarang memiliki panjang maksimum

Masalah Pelacakan #10691

Perilaku yang lama

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.

Perilaku yang baru

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.

Mengapa

nvarchar(max) kolom tidak efisien dan tidak perlu ketika panjang semua nilai yang mungkin diketahui.

Mitigasi

Ukuran kolom dapat dibuat secara eksplisit tidak terbatas:

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

Nilai kunci SQL Server dibandingkan secara tidak peka huruf besar/kecil

Masalah Pelacakan #27526

Perilaku yang lama

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.

Perilaku yang baru

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.

Mengapa

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.

Mitigasi

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);
        });
}