Perubahan mendasar di EF Core 6.0
PERUBAHAN API dan perilaku berikut berpotensi memutus pembaruan aplikasi yang ada ke EF Core 6.0.
Kerangka Target
EF Core 6.0 menargetkan .NET 6. Aplikasi yang menargetkan versi .NET, .NET Core, dan .NET Framework yang lebih lama perlu menargetkan .NET 6 untuk menggunakan EF Core 6.0.
Ringkasan
* Perubahan ini sangat menarik bagi penulis penyedia database dan ekstensi.
Perubahan berdampak tinggi
Dependen opsional berlapis berbagi tabel dan tanpa properti yang diperlukan tidak diizinkan
Perilaku yang lama
Model dengan dependen opsional berlapis yang berbagi tabel dan tanpa properti yang diperlukan diizinkan, tetapi dapat mengakibatkan kehilangan data saat mengkueri data lalu menyimpan lagi. Misalnya, pertimbangkan model berikut:
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
public ContactInfo ContactInfo { get; set; }
}
[Owned]
public class ContactInfo
{
public string Phone { get; set; }
public Address Address { get; set; }
}
[Owned]
public class Address
{
public string House { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string Postcode { get; set; }
}
Tidak ada properti dalam ContactInfo
atau Address
yang diperlukan, dan semua jenis entitas ini dipetakan ke tabel yang sama. Aturan untuk dependen opsional (dibandingkan dengan dependen yang diperlukan) mengatakan bahwa jika semua kolom untuk ContactInfo
null, maka tidak ada instans ContactInfo
yang akan dibuat saat mengkueri pemilik Customer
. Namun, ini juga berarti bahwa tidak ada instans Address
yang akan dibuat, bahkan jika Address
kolom non-null.
Perilaku yang baru
Mencoba menggunakan model ini sekarang akan melemparkan pengecualian berikut:
System.InvalidOperationException: Jenis entitas 'ContactInfo' adalah dependen opsional menggunakan berbagi tabel dan berisi dependen lain tanpa properti non bersama yang diperlukan untuk mengidentifikasi apakah entitas ada. Jika semua properti nullable berisi nilai null dalam database, maka instans objek tidak akan dibuat dalam kueri yang menyebabkan nilai dependen berlapis hilang. Tambahkan properti yang diperlukan untuk membuat instans dengan nilai null untuk properti lain atau tandai navigasi masuk sebagaimana diperlukan untuk selalu membuat instans.
Ini mencegah kehilangan data saat mengkueri dan menyimpan data.
Mengapa
Menggunakan model dengan dependen opsional berlapis yang berbagi tabel dan tanpa properti yang diperlukan sering mengakibatkan kehilangan data senyap.
Mitigasi
Hindari menggunakan dependen opsional yang berbagi tabel dan tanpa properti yang diperlukan. Ada tiga cara mudah untuk melakukan ini:
Buat dependen diperlukan. Ini berarti bahwa entitas dependen akan selalu memiliki nilai setelah dikueri, bahkan jika semua propertinya null. Contohnya:
public class Customer { public int Id { get; set; } public string Name { get; set; } [Required] public Address Address { get; set; } }
Atau:
modelBuilder.Entity<Customer>( b => { b.OwnsOne(e => e.Address); b.Navigation(e => e.Address).IsRequired(); });
Pastikan dependen berisi setidaknya satu properti yang diperlukan.
Petakan dependen opsional ke tabel mereka sendiri, alih-alih berbagi tabel dengan prinsipal. Contohnya:
modelBuilder.Entity<Customer>( b => { b.ToTable("Customers"); b.OwnsOne(e => e.Address, b => b.ToTable("CustomerAddresses")); });
Masalah dengan dependen opsional dan contoh mitigasi ini disertakan dalam dokumentasi untuk Apa yang baru dalam EF Core 6.0.
Perubahan dampak sedang
Mengubah pemilik entitas yang dimiliki sekarang melemparkan pengecualian
Perilaku yang lama
Dimungkinkan untuk menetapkan ulang entitas yang dimiliki ke entitas pemilik yang berbeda.
Perilaku yang baru
Tindakan ini sekarang akan melemparkan pengecualian:
Properti '{entityType}. {property}' adalah bagian dari kunci sehingga tidak dapat dimodifikasi atau ditandai sebagai dimodifikasi. Untuk mengubah prinsipal entitas yang ada dengan kunci asing yang mengidentifikasi, pertama-tama hapus dependen dan panggil 'SaveChanges', lalu kaitkan dependen dengan prinsipal baru.
Mengapa
Meskipun kita tidak memerlukan properti kunci untuk ada pada jenis yang dimiliki, EF masih akan membuat properti bayangan untuk digunakan sebagai kunci utama dan kunci asing yang menunjuk ke pemilik. Ketika entitas pemilik diubah, hal ini menyebabkan nilai kunci asing pada entitas yang dimiliki berubah, dan karena entitas tersebut juga digunakan sebagai kunci utama, hal ini mengakibatkan identitas entitas berubah. Ini belum sepenuhnya didukung di EF Core dan hanya diizinkan secara kondisional untuk entitas yang dimiliki, terkadang mengakibatkan status internal menjadi tidak konsisten.
Mitigasi
Alih-alih menetapkan instans yang dimiliki yang sama ke pemilik baru, Anda dapat menetapkan salinan dan menghapus yang lama.
Azure Cosmos DB: Jenis entitas terkait ditemukan sebagai milik
Masalah Pelacakan #24803Apa yang baru: Default ke kepemilikan implisit
Perilaku yang lama
Seperti di penyedia lain, jenis entitas terkait ditemukan sebagai jenis normal (tidak dimiliki).
Perilaku yang baru
Jenis entitas terkait sekarang akan dimiliki oleh jenis entitas tempat mereka ditemukan. Hanya jenis entitas yang sesuai dengan properti yang DbSet<TEntity> akan ditemukan sebagai tidak dimiliki.
Mengapa
Perilaku ini mengikuti pola umum pemodelan data di Azure Cosmos DB menyematkan data terkait ke dalam satu dokumen. Azure Cosmos DB tidak secara asli mendukung bergabung dengan dokumen yang berbeda, sehingga pemodelan entitas terkait karena yang tidak dimiliki memiliki kegunaan terbatas.
Mitigasi
Untuk mengonfigurasi jenis entitas agar menjadi panggilan yang tidak dimiliki modelBuilder.Entity<MyEntity>();
SQLite: Koneksi dikumpulkan
Masalah Pelacakan #13837Apa yang baru: Default ke kepemilikan implisit
Perilaku yang lama
Sebelumnya, koneksi di Microsoft.Data.Sqlite tidak dikumpulkan.
Perilaku yang baru
Mulai dari 6.0, koneksi sekarang dikumpulkan secara default. Ini menghasilkan file database tetap terbuka oleh proses bahkan setelah objek koneksi ADO.NET ditutup.
Mengapa
Mengumpulkan koneksi yang mendasar sangat meningkatkan performa membuka dan menutup objek koneksi ADO.NET. Ini sangat terlihat untuk skenario di mana membuka koneksi yang mendasarinya mahal seperti dalam kasus enkripsi, atau dalam skenario di mana ada banyak koneksi berumur pendek ke database.
Mitigasi
Pengumpulan koneksi dapat dinonaktifkan dengan menambahkan Pooling=False
ke string koneksi.
Beberapa skenario (seperti menghapus file database) sekarang mungkin mengalami kesalahan yang menyatakan bahwa file masih digunakan. Anda dapat menghapus kumpulan koneksi secara manual sebelum melakukan operasi file menggunakan SqliteConnection.ClearPool()
.
SqliteConnection.ClearPool(connection);
File.Delete(databaseFile);
Hubungan banyak ke banyak tanpa entitas gabungan yang dipetakan sekarang dibuat perancah
Perilaku yang lama
Perancah (rekayasa terbalik) DbContext
jenis entitas dan dari database yang ada selalu dipetakan secara eksplisit menggabungkan jenis entitas untuk hubungan banyak-ke-banyak.
Perilaku yang baru
Tabel gabungan sederhana yang hanya berisi dua properti kunci asing ke tabel lain tidak lagi dipetakan ke jenis entitas eksplisit, tetapi sebaliknya dipetakan sebagai hubungan banyak-ke-banyak antara dua tabel yang bergabung.
Mengapa
Hubungan banyak ke banyak tanpa jenis gabungan eksplisit diperkenalkan dalam EF Core 5.0 dan merupakan cara yang lebih bersih dan lebih alami untuk mewakili tabel gabungan sederhana.
Mitigasi
Ada dua mitigasi. Pendekatan yang disukai adalah memperbarui kode untuk menggunakan hubungan banyak ke banyak secara langsung. Sangat jarang bahwa jenis entitas gabungan perlu digunakan secara langsung ketika hanya berisi dua kunci asing untuk hubungan banyak-ke-banyak.
Secara bergantian, entitas gabungan eksplisit dapat ditambahkan kembali ke model EF. Misalnya, dengan asumsi hubungan banyak ke banyak antara Post
dan Tag
, tambahkan kembali jenis gabungan dan navigasi menggunakan kelas parsial:
public partial class PostTag
{
public int PostsId { get; set; }
public int TagsId { get; set; }
public virtual Post Posts { get; set; }
public virtual Tag Tags { get; set; }
}
public partial class Post
{
public virtual ICollection<PostTag> PostTags { get; set; }
}
public partial class Tag
{
public virtual ICollection<PostTag> PostTags { get; set; }
}
Kemudian tambahkan konfigurasi untuk jenis gabungan dan navigasi ke kelas parsial untuk DbContext:
public partial class DailyContext
{
partial void OnModelCreatingPartial(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>(entity =>
{
entity.HasMany(d => d.Tags)
.WithMany(p => p.Posts)
.UsingEntity<PostTag>(
l => l.HasOne<Tag>(e => e.Tags).WithMany(e => e.PostTags).HasForeignKey(e => e.TagsId),
r => r.HasOne<Post>(e => e.Posts).WithMany(e => e.PostTags).HasForeignKey(e => e.PostsId),
j =>
{
j.HasKey("PostsId", "TagsId");
j.ToTable("PostTag");
});
});
}
}
Terakhir, hapus konfigurasi yang dihasilkan untuk hubungan banyak ke banyak dari konteks perancah. Ini diperlukan karena jenis entitas gabungan perancah harus dihapus dari model sebelum jenis eksplisit dapat digunakan. Kode ini perlu dihapus setiap kali konteks di-scaffold, tetapi karena kode di atas berada di kelas parsial, konteks akan bertahan.
Perhatikan bahwa dengan konfigurasi ini, entitas gabungan dapat digunakan secara eksplisit, seperti pada versi EF Core sebelumnya. Namun, hubungan ini juga dapat digunakan sebagai hubungan banyak ke banyak. Ini berarti bahwa memperbarui kode seperti ini dapat menjadi solusi sementara sementara sisa kode diperbarui untuk menggunakan hubungan sebagai banyak-ke-banyak dengan cara alami.
Perubahan berdampak rendah
Membersihkan pemetaan antara nilai DeleteBehavior dan ON DELETE
Perilaku yang lama
Beberapa pemetaan antara perilaku hubungan OnDelete()
dan perilaku kunci ON DELETE
asing dalam database tidak konsisten dalam Migrasi dan Perancah.
Perilaku yang baru
Tabel berikut mengilustrasikan perubahan untuk Migrasi.
OnDelete() | SAAT DIHAPUS |
---|---|
NoAction | TIDAK ADA TINDAKAN |
ClientNoAction | TIDAK ADA TINDAKAN |
Membatasi | BATASI |
Cascade | CASCADE |
ClientCascade | |
SetNull | SET NULL |
ClientSetNull |
Perubahan untuk Perancah adalah sebagai berikut.
SAAT DIHAPUS | OnDelete() |
---|---|
TIDAK ADA TINDAKAN | ClientSetNull |
BATASI | |
CASCADE | Cascade |
SET NULL | SetNull |
Mengapa
Pemetaan baru lebih konsisten. Perilaku database default NO ACTION sekarang lebih disukai daripada perilaku RESTRICT yang lebih ketat dan kurang berkinerja.
Mitigasi
Perilaku OnDelete() default dari hubungan opsional adalah ClientSetNull. Pemetaannya telah berubah dari RESTRICT ke NO ACTION. Ini dapat menyebabkan banyak operasi dihasilkan dalam migrasi pertama Anda ditambahkan setelah meningkatkan ke EF Core 6.0.
Anda dapat memilih untuk menerapkan operasi ini atau menghapusnya secara manual dari migrasi karena tidak memiliki dampak fungsional pada EF Core.
SQL Server tidak mendukung RESTRICT, sehingga kunci asing ini sudah dibuat menggunakan NO ACTION. Operasi migrasi tidak akan berpengaruh pada SQL Server dan aman untuk dihapus.
Database dalam memori memvalidasi properti yang diperlukan tidak berisi null
Perilaku yang lama
Database dalam memori diizinkan menyimpan nilai null bahkan ketika properti dikonfigurasi sesuai kebutuhan.
Perilaku yang baru
Database dalam memori melempar Microsoft.EntityFrameworkCore.DbUpdateException
kapan SaveChanges
atau SaveChangesAsync
dipanggil dan properti yang diperlukan diatur ke null.
Mengapa
Perilaku database dalam memori sekarang cocok dengan perilaku database lain.
Mitigasi
Perilaku sebelumnya (yaitu tidak memeriksa nilai null) dapat dipulihkan saat mengonfigurasi penyedia dalam memori. Contohnya:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseInMemoryDatabase("MyDatabase", b => b.EnableNullChecks(false));
}
Menghapus ORDER BY terakhir saat bergabung untuk koleksi
Perilaku yang lama
Saat melakukan SQL JOIN pada koleksi (hubungan satu-ke-banyak), EF Core digunakan untuk menambahkan ORDER BY untuk setiap kolom kunci tabel yang digabungkan. Misalnya, memuat semua Blog dengan Posting terkait mereka dilakukan melalui SQL berikut:
SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId], [p].[PostId]
Pesanan ini diperlukan untuk materialisasi entitas yang tepat.
Perilaku yang baru
ORDER BY terakhir untuk gabungan koleksi sekarang dihilangkan:
SELECT [b].[BlogId], [b].[Name], [p].[PostId], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Post] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]
ORDER BY untuk kolom ID Postingan tidak lagi dihasilkan.
Mengapa
Setiap ORDER BY memberlakukan pekerjaan tambahan di sisi database, dan pengurutan terakhir tidak diperlukan untuk kebutuhan materialisasi EF Core. Data menunjukkan bahwa menghapus urutan terakhir ini dapat menghasilkan peningkatan performa yang signifikan dalam beberapa skenario.
Mitigasi
Jika aplikasi Anda mengharapkan entitas yang bergabung dikembalikan dalam urutan tertentu, buat itu eksplisit dengan menambahkan operator LINQ OrderBy
ke kueri Anda.
DbSet tidak lagi mengimplementasikan IAsyncEnumerable
Perilaku yang lama
DbSet<TEntity>, yang digunakan untuk menjalankan kueri pada DbContext, digunakan untuk mengimplementasikan IAsyncEnumerable<T>.
Perilaku yang baru
DbSet<TEntity> tidak lagi secara langsung mengimplementasikan IAsyncEnumerable<T>.
Mengapa
DbSet<TEntity> awalnya dibuat untuk mengimplementasikan IAsyncEnumerable<T> terutama untuk memungkinkan enumerasi langsung di atasnya melalui foreach
konstruksi. Sayangnya, ketika proyek juga mereferensikan System.Linq.Async untuk menyusun sisi klien operator LINQ asinkron, ini mengakibatkan kesalahan pemanggilan ambigu antara operator yang ditentukan dan IQueryable<T>
yang ditentukan atas IAsyncEnumerable<T>
. C# 9 menambahkan dukungan ekstensi GetEnumerator
untuk foreach
perulangan, menghapus alasan utama asli untuk mereferensikan IAsyncEnumerable
.
Sebagian besar DbSet
penggunaan akan terus berfungsi apa adanya, karena mereka menyusun operator LINQ di atas DbSet
, menghitungnya, dll. Satu-satunya penggunaan yang rusak adalah yang mencoba untuk melemparkan DbSet
langsung ke IAsyncEnumerable
.
Mitigasi
Jika Anda perlu merujuk sebagai DbSet<TEntity> IAsyncEnumerable<T>, panggil DbSet<TEntity>.AsAsyncEnumerable untuk secara eksplisit melemparkannya.
Jenis entitas pengembalian TVF juga dipetakan ke tabel secara default
Perilaku yang lama
Jenis entitas tidak dipetakan ke tabel secara default saat digunakan sebagai jenis pengembalian TVF yang dikonfigurasi dengan HasDbFunction.
Perilaku yang baru
Jenis entitas yang digunakan sebagai jenis pengembalian TVF mempertahankan pemetaan tabel default.
Mengapa
Tidak intuitif bahwa mengonfigurasi TVF menghapus pemetaan tabel default untuk jenis entitas pengembalian.
Mitigasi
Untuk menghapus pemetaan tabel default, panggil ToTable(EntityTypeBuilder, String):
modelBuilder.Entity<MyEntity>().ToTable((string?)null));
Periksa keunikan nama batasan sekarang divalidasi
Perilaku yang lama
Periksa batasan dengan nama yang sama diizinkan untuk dideklarasikan dan digunakan pada tabel yang sama.
Perilaku yang baru
Mengonfigurasi dua batasan pemeriksaan secara eksplisit dengan nama yang sama pada tabel yang sama sekarang akan menghasilkan pengecualian. Periksa batasan yang dibuat oleh konvensi akan diberi nama unik.
Mengapa
Sebagian besar database tidak mengizinkan dua batasan pemeriksaan dengan nama yang sama dibuat pada tabel yang sama, dan beberapa mengharuskannya unik bahkan di seluruh tabel. Ini akan mengakibatkan pengecualian dilemparkan saat menerapkan migrasi.
Mitigasi
Dalam beberapa kasus, nama batasan pemeriksaan yang valid mungkin berbeda karena perubahan ini. Untuk menentukan nama yang diinginkan secara eksplisit, panggil HasName:
modelBuilder.Entity<MyEntity>().HasCheckConstraint("CK_Id", "Id > 0", c => c.HasName("CK_MyEntity_Id"));
Menambahkan antarmuka Metadata IReadOnly dan metode ekstensi yang dihapus
Perilaku yang lama
Ada tiga set antarmuka metadata: IModel, IMutableModel dan IConventionModel serta metode ekstensi.
Perilaku yang baru
Sekumpulan IReadOnly
antarmuka baru telah ditambahkan, misalnya IReadOnlyModel. Metode ekstensi yang sebelumnya ditentukan untuk antarmuka metadata telah dikonversi ke metode antarmuka default.
Mengapa
Metode antarmuka default memungkinkan implementasi ditimpa, ini dimanfaatkan oleh implementasi model run-time baru untuk menawarkan performa yang lebih baik.
Mitigasi
Perubahan ini seharusnya tidak memengaruhi sebagian besar kode. Namun, jika Anda menggunakan metode ekstensi melalui sintaks pemanggilan statis, itu perlu dikonversi ke sintaks pemanggilan instans.
IExecutionStrategy sekarang menjadi layanan singleton
Perilaku yang baru
IExecutionStrategy sekarang menjadi layanan singleton. Ini berarti bahwa setiap status tambahan dalam implementasi kustom akan tetap berada di antara eksekusi dan delegasi yang diteruskan ke ExecutionStrategy hanya akan dijalankan sekali.
Mengapa
Pengurangan alokasi ini pada dua jalur panas di EF.
Mitigasi
Implementasi yang berasal dari ExecutionStrategy harus menghapus status apa pun di OnFirstExecution().
Logika kondisional dalam delegasi yang diteruskan ke ExecutionStrategy harus dipindahkan ke implementasi IExecutionStrategykustom .
SQL Server: Lebih banyak kesalahan dianggap sementara
Perilaku yang baru
Kesalahan yang tercantum dalam masalah di atas sekarang dianggap sementara. Saat menggunakan strategi eksekusi default (non-coba lagi), kesalahan ini sekarang akan dibungkus dalam instans pengecualian tambahan.
Mengapa
Kami terus mengumpulkan umpan balik dari pengguna dan tim SQL Server di mana kesalahan harus dianggap sementara.
Mitigasi
Untuk mengubah serangkaian kesalahan yang dianggap sementara, gunakan strategi eksekusi kustom yang dapat berasal dari SqlServerRetryingExecutionStrategy - Ketahanan Koneksi - EF Core.
Azure Cosmos DB: Lebih banyak karakter lolos dalam nilai 'id'
Perilaku yang lama
Di EF Core 5, hanya '|'
lolos dalam id
nilai.
Perilaku yang baru
Di EF Core 6, '/'
, '\'
, '?'
dan '#'
juga lolos dalam id
nilai.
Mengapa
Karakter ini tidak valid, seperti yang didokumenkan dalam Resource.Id. Menggunakannya di id
akan menyebabkan kueri gagal.
Mitigasi
Anda dapat mengambil alih nilai yang dihasilkan dengan mengaturnya sebelum entitas ditandai sebagai Added
:
var entry = context.Attach(entity);
entry.Property("__id").CurrentValue = "MyEntity|/\\?#";
entry.State = EntityState.Added;
Beberapa layanan Singleton sekarang Terlingkup
Perilaku yang baru
Banyak layanan kueri dan beberapa layanan waktu desain yang terdaftar seperti Singleton
yang sekarang terdaftar sebagai Scoped
.
Mengapa
Masa pakai harus diubah untuk memungkinkan fitur baru - untuk DefaultTypeMapping memengaruhi kueri.
Masa pakai layanan waktu desain telah disesuaikan agar sesuai dengan masa pakai layanan run-time untuk menghindari kesalahan saat menggunakan keduanya.
Mitigasi
Gunakan TryAdd untuk mendaftarkan layanan EF Core menggunakan masa pakai default. Hanya gunakan TryAddProviderSpecificServices untuk layanan yang tidak ditambahkan oleh EF.
API penembolokan baru untuk ekstensi yang menambahkan atau mengganti layanan
Perilaku yang lama
Di EF Core 5, GetServiceProviderHashCode dikembalikan long
dan digunakan langsung sebagai bagian dari kunci cache untuk penyedia layanan.
Perilaku yang baru
GetServiceProviderHashCode sekarang mengembalikan int
dan hanya digunakan untuk menghitung kode hash kunci cache untuk penyedia layanan.
Selain itu, ShouldUseSameServiceProvider perlu diimplementasikan untuk menunjukkan apakah objek saat ini mewakili konfigurasi layanan yang sama dan dengan demikian dapat menggunakan penyedia layanan yang sama.
Mengapa
Hanya menggunakan kode hash sebagai bagian dari kunci cache mengakibatkan tabrakan sesekali yang sulit didiagnosis dan diperbaiki. Metode tambahan memastikan bahwa penyedia layanan yang sama hanya digunakan jika sesuai.
Mitigasi
Banyak ekstensi tidak mengekspos opsi apa pun yang memengaruhi layanan terdaftar dan dapat menggunakan implementasi berikut dari ShouldUseSameServiceProvider:
private sealed class ExtensionInfo : DbContextOptionsExtensionInfo
{
public ExtensionInfo(IDbContextOptionsExtension extension)
: base(extension)
{
}
...
public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other)
=> other is ExtensionInfo;
}
Jika tidak, predikat tambahan harus ditambahkan untuk membandingkan semua opsi yang relevan.
Prosedur inisialisasi model rekam jepret dan waktu desain baru
Perilaku yang lama
Dalam EF Core 5, konvensi tertentu perlu dipanggil sebelum model rekam jepret siap digunakan.
Perilaku yang baru
IModelRuntimeInitializer diperkenalkan untuk menyembunyikan beberapa langkah yang diperlukan, dan model run-time diperkenalkan yang tidak memiliki semua metadata migrasi, sehingga model waktu desain harus digunakan untuk perbedaan model.
Mengapa
IModelRuntimeInitializer mengabstraksi langkah-langkah finalisasi model, sehingga ini sekarang dapat diubah tanpa melanggar perubahan lebih lanjut bagi pengguna.
Model run-time yang dioptimalkan diperkenalkan untuk meningkatkan performa run-time. Ini memiliki beberapa pengoptimalan, salah satunya adalah menghapus metadata yang tidak digunakan pada run-time.
Mitigasi
Cuplikan berikut menggambarkan cara memeriksa apakah model saat ini berbeda dari model rekam jepret:
var snapshotModel = migrationsAssembly.ModelSnapshot?.Model;
if (snapshotModel is IMutableModel mutableModel)
{
snapshotModel = mutableModel.FinalizeModel();
}
if (snapshotModel != null)
{
snapshotModel = context.GetService<IModelRuntimeInitializer>().Initialize(snapshotModel);
}
var hasDifferences = context.GetService<IMigrationsModelDiffer>().HasDifferences(
snapshotModel?.GetRelationalModel(),
context.GetService<IDesignTimeModel>().Model.GetRelationalModel());
Cuplikan ini menunjukkan cara menerapkan IDesignTimeDbContextFactory<TContext> dengan membuat model secara eksternal dan memanggil UseModel:
internal class MyDesignContext : IDesignTimeDbContextFactory<MyContext>
{
public TestContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder();
optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DB"));
var modelBuilder = SqlServerConventionSetBuilder.CreateModelBuilder();
CustomizeModel(modelBuilder);
var model = modelBuilder.Model.FinalizeModel();
var serviceContext = new MyContext(optionsBuilder.Options);
model = serviceContext.GetService<IModelRuntimeInitializer>().Initialize(model);
return new MyContext(optionsBuilder.Options);
}
}
OwnedNavigationBuilder.HasIndex
mengembalikan jenis yang berbeda sekarang
Perilaku yang lama
Di EF Core 5, HasIndex dikembalikan IndexBuilder<TEntity>
di mana TEntity
adalah jenis pemilik.
Perilaku yang baru
HasIndex sekarang mengembalikan IndexBuilder<TDependentEntity>
, di mana TDependentEntity
adalah jenis yang dimiliki.
Mengapa
Objek penyusun yang dikembalikan tidak di ketik dengan benar.
Mitigasi
Kompilasi ulang rakitan Anda terhadap versi terbaru EF Core akan cukup untuk memperbaiki masalah apa pun yang disebabkan oleh perubahan ini.
DbFunctionBuilder.HasSchema(null)
Mengabaikan [DbFunction(Schema = "schema")]
Perilaku yang lama
Di EF Core 5, panggilan HasSchema dengan null
nilai tidak menyimpan sumber konfigurasi, sehingga DbFunctionAttribute dapat mengambil alihnya.
Perilaku yang baru
Memanggil HasSchema dengan null
nilai sekarang menyimpan sumber konfigurasi dan mencegah atribut menimpanya.
Mengapa
Konfigurasi yang ditentukan dengan ModelBuilder API tidak boleh diambil alih oleh anotasi data.
Mitigasi
HasSchema
Hapus panggilan untuk membiarkan atribut mengonfigurasi skema.
Navigasi yang telah diinisialisasi sebelumnya ditimpa oleh nilai dari kueri database
Perilaku yang lama
Properti navigasi yang diatur ke objek kosong dibiarkan tidak berubah untuk kueri pelacakan, tetapi ditimpa untuk kueri non-pelacakan. Misalnya, pertimbangkan jenis entitas berikut:
public class Foo
{
public int Id { get; set; }
public Bar Bar { get; set; } = new(); // Don't do this.
}
public class Bar
{
public int Id { get; set; }
}
Kueri tanpa pelacakan untuk Foo
menyertakan Bar
diatur Foo.Bar
ke entitas yang dikueri dari database. Misalnya, kode ini:
var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");
Dicetak Foo.Bar.Id = 1
.
Namun, kueri yang sama yang dijalankan untuk pelacakan tidak menimpa Foo.Bar
entitas yang dikueri dari database. Misalnya, kode ini:
var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");
Dicetak Foo.Bar.Id = 0
.
Perilaku yang baru
Di EF Core 6.0, perilaku kueri pelacakan sekarang cocok dengan kueri tanpa pelacakan. Ini berarti bahwa kedua kode ini:
var foo = context.Foos.AsNoTracking().Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");
Dan kode ini:
var foo = context.Foos.Include(e => e.Bar).Single();
Console.WriteLine($"Foo.Bar.Id = {foo.Bar.Id}");
Cetak Foo.Bar.Id = 1
.
Mengapa
Ada dua alasan untuk membuat perubahan ini:
- Untuk memastikan bahwa kueri pelacakan dan tanpa pelacakan memiliki perilaku yang konsisten.
- Ketika database dikueri, masuk akal untuk mengasumsikan bahwa kode aplikasi ingin mendapatkan kembali nilai yang disimpan dalam database.
Mitigasi
Ada dua mitigasi:
- Jangan mengkueri objek dari database yang seharusnya tidak disertakan dalam hasil. Misalnya, dalam cuplikan kode di atas, jangan
Include
Foo.Bar
jikaBar
instans tidak boleh dikembalikan dari database dan disertakan dalam hasilnya. - Atur nilai navigasi setelah mengkueri dari database. Misalnya, dalam cuplikan kode di atas, panggil
foo.Bar = new()
setelah menjalankan kueri.
Selain itu, pertimbangkan untuk tidak menginisialisasi instans entitas terkait ke objek default. Ini menyiratkan bahwa instans terkait adalah entitas baru, tidak disimpan ke database, tanpa set nilai kunci. Jika sebagai gantinya entitas terkait memang ada dalam database, maka data dalam kode pada dasarnya berselisih dengan data yang disimpan dalam database.
Nilai string enum yang tidak diketahui dalam database tidak dikonversi ke default enum saat dikueri
Perilaku yang lama
Properti Enum dapat dipetakan ke kolom string dalam database menggunakan HasConversion<string>()
atau EnumToStringConverter
. Ini menghasilkan EF Core yang mengonversi nilai string di kolom ke anggota yang cocok dari jenis enum .NET. Namun, jika nilai string tidak cocok dan anggota enum, maka properti diatur ke nilai default untuk enum.
Perilaku yang baru
EF Core 6.0 sekarang melempar InvalidOperationException
dengan pesan "Tidak dapat mengonversi nilai string '{value}
' dari database ke nilai apa pun dalam enum '{enumType}
' yang dipetakan."
Mengapa
Mengonversi ke nilai default dapat mengakibatkan kerusakan database jika entitas nantinya disimpan kembali ke database.
Mitigasi
Idealnya, pastikan bahwa kolom database hanya berisi nilai yang valid. Secara bergantian, terapkan ValueConverter
dengan perilaku lama.
DbFunctionBuilder.HasTranslation sekarang menyediakan argumen fungsi sebagai IReadOnlyList daripada IReadOnlyCollection
Perilaku yang lama
Saat mengonfigurasi terjemahan untuk fungsi yang ditentukan pengguna menggunakan HasTranslation
metode, argumen untuk fungsi disediakan sebagai IReadOnlyCollection<SqlExpression>
.
Perilaku yang baru
Di EF Core 6.0, argumen sekarang disediakan sebagai IReadOnlyList<SqlExpression>
.
Mengapa
IReadOnlyList
memungkinkan untuk menggunakan pengindeks, sehingga argumen sekarang lebih mudah diakses.
Mitigasi
Tidak ada. IReadOnlyList
IReadOnlyCollection
mengimplementasikan antarmuka, sehingga transisi harus mudah.
Pemetaan tabel default tidak dihapus saat entitas dipetakan ke fungsi bernilai tabel
Perilaku yang lama
Ketika entitas dipetakan ke fungsi bernilai tabel, pemetaan defaultnya ke tabel dihapus.
Perilaku yang baru
Di EF Core 6.0, entitas masih dipetakan ke tabel menggunakan pemetaan default, bahkan jika juga dipetakan ke fungsi bernilai tabel.
Mengapa
Fungsi bernilai tabel yang mengembalikan entitas sering digunakan baik sebagai pembantu atau untuk merangkum operasi yang mengembalikan kumpulan entitas, bukan sebagai pengganti ketat dari seluruh tabel. Perubahan ini bertujuan agar lebih sejalan dengan kemungkinan niat pengguna.
Mitigasi
Pemetaan ke tabel dapat dinonaktifkan secara eksplisit dalam konfigurasi model:
modelBuilder.Entity<MyEntity>().ToTable((string)null);
dotnet-ef menargetkan .NET 6
Perilaku yang lama
Perintah dotnet-ef telah menargetkan .NET Core 3.1 untuk sementara waktu sekarang. Ini memungkinkan Anda menggunakan versi alat yang lebih baru tanpa menginstal versi runtime .NET yang lebih baru.
Perilaku yang baru
Di EF Core 6.0.6, alat dotnet-ef sekarang menargetkan .NET 6. Anda masih dapat menggunakan alat ini pada proyek yang menargetkan versi .NET dan .NET Core yang lebih lama, tetapi Anda harus menginstal runtime .NET 6 untuk menjalankan alat.
Mengapa
.NET 6.0.200 SDK memperbarui perilaku dotnet tool install
pada osx-arm64 untuk membuat shim osx-x64 untuk alat yang menargetkan .NET Core 3.1. Untuk mempertahankan pengalaman default kerja untuk dotnet-ef, kami harus memperbaruinya ke target .NET 6.
Mitigasi
Untuk menjalankan dotnet-ef tanpa menginstal runtime .NET 6, Anda dapat menginstal versi alat yang lebih lama:
dotnet tool install dotnet-ef --version 3.1.*
IModelCacheKeyFactory
implementasi mungkin perlu diperbarui untuk menangani penembolokan waktu desain
Perilaku yang lama
IModelCacheKeyFactory
tidak memiliki opsi untuk menyimpan model waktu desain secara terpisah dari model runtime.
Perilaku yang baru
IModelCacheKeyFactory
memiliki kelebihan beban baru yang memungkinkan model waktu desain di-cache secara terpisah dari model runtime. Tidak menerapkan metode ini dapat mengakibatkan pengecualian yang mirip dengan:
System.InvalidOperationException: 'Konfigurasi yang diminta tidak disimpan dalam model yang dioptimalkan baca, silakan gunakan 'DbContext.GetService<IDesignTimeModel>(). Model'.'
Mengapa
Implementasi model yang dikompilasi memerlukan pemisahan waktu desain (digunakan saat membangun model) dan runtime (digunakan saat mengeksekusi kueri, dll.) model. Jika kode runtime memerlukan akses ke informasi waktu desain, model waktu desain harus di-cache.
Mitigasi
Terapkan kelebihan beban baru. Contohnya:
public object Create(DbContext context, bool designTime)
=> context is DynamicContext dynamicContext
? (context.GetType(), dynamicContext.UseIntProperty, designTime)
: (object)context.GetType();
Navigasi '{navigation}' diabaikan dari 'Sertakan' dalam kueri karena perbaikan akan secara otomatis mengisinya. Jika ada navigasi lebih lanjut yang ditentukan dalam 'Sertakan' setelahnya, navigasi tersebut akan diabaikan. Berjalan kembali di pohon include tidak diperbolehkan.
NavigationBaseIncludeIgnored
sekarang merupakan kesalahan secara default
Perilaku yang lama
Peristiwa CoreEventId.NavigationBaseIncludeIgnored
dicatat sebagai peringatan secara default.
Perilaku yang baru
Peristiwa CoreEventId.NavigationBaseIncludeIgnored
dicatat sebagai kesalahan secara default dan menyebabkan pengecualian dilemparkan.
Mengapa
Pola kueri ini tidak diizinkan, jadi EF Core sekarang melempar untuk menunjukkan bahwa kueri harus diperbarui.
Mitigasi
Perilaku lama dapat dipulihkan dengan mengonfigurasi peristiwa sebagai peringatan. Contohnya:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.ConfigureWarnings(b => b.Warn(CoreEventId.NavigationBaseIncludeIgnored));