EF Core 7.0 的重大變更 (EF7)

此頁面記載 API 和行為變更,這些變更可能會中斷從 EF Core 6 更新至 EF Core 7 的現有應用程式。 如果從舊版 EF Core 更新,請務必檢閱先前的重大變更:

目標 Framework

EF Core 7.0 的目標是 .NET 6。 這表示以 .NET 6 為目標的現有應用程式可以繼續執行此動作。 以舊版 .NET、.NET Core 和 .NET Framework 版本為目標的應用程式必須以 .NET 6 或 .NET 7 為目標,才能使用 EF Core 7.0。

摘要

重大變更 影響
EncryptSQL Server 連線的預設值為true
某些警告預設會再次擲回例外狀況
具有觸發程式或特定計算數據行的 SQL Server 數據表現在需要特殊的 EF Core 設定
具有 AFTER 觸發程式和虛擬數據表的 SQLite 數據表現在需要特殊的 EF Core 設定
不會自動刪除選擇性關聯性的孤立相依專案
搭配 SQL Server 使用 TPT 對應時,數據表之間會設定串聯刪除
未使用預先寫入記錄時,SQLite 上忙碌/鎖定錯誤的機率較高
索引鍵屬性可能需要設定提供者值比較子
檢查條件約束和其他數據表 Facet 現在已在數據表上設定
不會修正從新實體巡覽至已刪除的實體
使用 FromSqlRaw 錯誤提供者的相關方法會擲回
Scaffolded OnConfiguring 不再呼叫 IsConfigured

高影響變更

EncryptSQL Server 連線的預設值為true

追蹤問題:SqlClient #1210

重要

這是 Microsoft.Data.SqlClient 套件中的嚴重中斷性變更。 EF Core 中無法執行任何動作來還原或減輕這項變更。 請直接意見反應給 Microsoft.Data.SqlClient GitHub 存放庫,或連絡 Microsoft 支援服務 Professional 以取得其他問題或說明。

舊的行為

預設會使用 Encrypt=False SqlClient 連接字串。 這可讓本機伺服器沒有有效憑證的開發計算機上連線。

新的行為

預設會使用 Encrypt=True SqlClient 連接字串。 這表示:

  • 伺服器必須使用有效的憑證進行設定
  • 客戶端必須信任此憑證

如果不符合這些條件, SqlException 則會擲回 。 例如:

已順利建立與伺服器的連接,但隨後在登入過程中發生錯誤。 (提供者:SSL 提供者,錯誤:0 - 憑證鏈結是由不受信任的授權單位所簽發。

原因為何

這項變更是為了確保連線是安全的,否則應用程式將無法連線。

風險降低

有三種方式可以繼續:

  1. 在伺服器上安裝有效的憑證。 請注意,這是相關的程式,需要取得憑證,並確保憑證是由用戶端信任的授權單位所簽署。
  2. 如果伺服器有憑證,但用戶端不信任憑證,則 TrustServerCertificate=True 允許略過一般信任機械師。
  3. 明確新增Encrypt=False至 連接字串。

警告

選項 2 和 3 都會讓伺服器處於潛在的不安全狀態。

某些警告預設會再次擲回例外狀況

追蹤問題 #29069

舊的行為

在 EF Core 6.0 中,SQL Server 提供者中的錯誤表示預設會記錄一些設定為擲回例外狀況的警告,但不會擲回例外狀況。 這些警告如下:

EventId 描述
RelationalEventId.AmbientTransactionWarning 當應用程式實際被忽略時,應用程式可能會預期使用環境交易。
RelationalEventId.IndexPropertiesBothMappedAndNotMappedToTable 索引會指定其中一些對應的屬性,其中有些屬性未對應至數據表中的數據行。
RelationalEventId.IndexPropertiesMappedToNonOverlappingTables 索引會指定對應至非重疊數據表上之數據行的屬性。
RelationalEventId.ForeignKeyPropertiesMappedToUnrelatedTables 外鍵會指定不會對應至相關數據表的屬性。

新的行為

從EF Core 7.0 開始,這些警告預設會再次引發例外狀況。

原因為何

這些問題很可能表示應用程式程式代碼中應該修正的錯誤。

風險降低

修正基礎問題,這是警告的原因。

或者,可以變更警告層級, 使其只記錄或完全隱藏。 例如:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .ConfigureWarnings(b => b.Ignore(RelationalEventId.AmbientTransactionWarning));

具有觸發程式或特定計算數據行的 SQL Server 數據表現在需要特殊的 EF Core 設定

追蹤問題 #27372

舊的行為

舊版的 SQL Server 提供者會透過較不有效率的技術儲存變更,一律可運作。

新的行為

根據預設,EF Core 現在會透過更有效率的技術儲存變更;不幸的是,如果目標數據表有資料庫觸發程式或特定類型的計算數據行,則 SQL Server 不支援這項技術。 如需詳細資訊, 請參閱 SQL Server 檔

原因為何

連結至新方法的效能改善已足夠重要,因此默認必須將它們帶入使用者。 同時,我們估計 EF Core 應用程式中資料庫觸發程式或受影響計算數據行的使用方式已足夠低,而負重大變更結果會超過效能提升。

風險降低

從EF Core 8.0 開始,可以明確設定 「OUTPUT」 子句的用法。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable(tb => tb.UseSqlOutputClause(false));
}

在 EF7 或更新版本中,如果目標數據表有觸發程式,您可以讓 EF Core 知道這一點,而 EF 會還原為先前效率較低的技術。 這可以透過設定對應的實體類型來完成,如下所示:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable(tb => tb.HasTrigger("SomeTrigger"));
}

請注意,這樣做實際上不會讓EF Core 以任何方式建立或管理觸發程式 - 它目前只會通知 EF Core,觸發程式存在於數據表上。 因此,可以使用任何觸發程序名稱。 指定觸發程式可用來還原舊的行為 ,即使數據表中實際上沒有觸發程式也一樣。

如果大部分或所有數據表都有觸發程式,您可以使用下列模型建置慣例,選擇不使用所有模型數據表的較新且有效率的技術:

public class BlankTriggerAddingConvention : IModelFinalizingConvention
{
    public virtual void ProcessModelFinalizing(
        IConventionModelBuilder modelBuilder,
        IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entityType in modelBuilder.Metadata.GetEntityTypes())
        {
            var table = StoreObjectIdentifier.Create(entityType, StoreObjectType.Table);
            if (table != null
                && entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(table.Value) == null)
                && (entityType.BaseType == null
                    || entityType.GetMappingStrategy() != RelationalAnnotationNames.TphMappingStrategy))
            {
                entityType.Builder.HasTrigger(table.Value.Name + "_Trigger");
            }

            foreach (var fragment in entityType.GetMappingFragments(StoreObjectType.Table))
            {
                if (entityType.GetDeclaredTriggers().All(t => t.GetDatabaseName(fragment.StoreObject) == null))
                {
                    entityType.Builder.HasTrigger(fragment.StoreObject.Name + "_Trigger");
                }
            }
        }
    }
}

藉由覆ConfigureConventions寫 ,在您的 上使用DbContext慣例:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Add(_ => new BlankTriggerAddingConvention());
}

這實際上會呼叫 HasTrigger 您所有模型的數據表,而不是您必須針對每個數據表手錶手動執行此動作。

具有 AFTER 觸發程式和虛擬數據表的 SQLite 數據表現在需要特殊的 EF Core 設定

追蹤問題 #29916

舊的行為

舊版 SQLite 提供者會透過較不有效率的技術來儲存變更,且一律有效。

新的行為

根據預設,EF Core 現在會使用 RETURNING 子句,透過更有效率的技術儲存變更。 不幸的是,如果目標數據表有資料庫 AFTER 觸發程式、是虛擬的,或是使用舊版 SQLite,SQLite 就不支援這項技術。 如需詳細資訊, 請參閱 SQLite 檔

原因為何

連結至新方法的簡化和效能改善已足夠重要,因此預設將他們帶到使用者非常重要。 同時,我們估計 EF Core 應用程式中的資料庫觸發程式和虛擬數據表使用量會足夠低,而負重大變更後果會超過效能提升。

風險降低

在 EF Core 8.0 中, UseSqlReturningClause 已引進 方法,以明確還原回較舊且效率較低的 SQL。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .ToTable(tb => tb.UseSqlReturningClause(false));
}

如果您仍在使用 EF Core 7.0,則可以在內容組態中插入下列程式代碼,以還原為整個應用程式的舊機制:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlite(...)
        .ReplaceService<IUpdateSqlGenerator, SqliteLegacyUpdateSqlGenerator>();

中等影響變更

不會自動刪除選擇性關聯性的孤立相依專案

追蹤問題 #27217

舊的行為

如果其外鍵可為 Null,則 關聯性是選擇性 的。 將外鍵設定為 null 可讓相依實體不存在,而不需要任何相關的主體實體。 選擇性關聯性可以設定為使用 串聯刪除,但這不是預設值。

選擇性的相依性可以藉由將其外鍵設定為 Null,或清除巡覽至或從中清除,以從其主體中斷。 在EF Core 6.0 中,當關聯性設定為串聯刪除時,這會導致相依項目刪除。

新的行為

從EF Core 7.0 開始,不再刪除相依專案。 請注意,如果主體已刪除,則相依專案仍會刪除,因為已針對關聯性設定串聯刪除。

原因為何

相依專案可以沒有與主體的任何關聯性存在,因此,分割關聯性不應造成實體遭到刪除。

風險降低

您可以明確刪除相依專案:

context.Remove(blog);

或者 SaveChanges 可以覆寫或攔截,以刪除沒有主體參考的相依專案。 例如:

context.SavingChanges += (c, _) =>
    {
        foreach (var entry in ((DbContext)c!).ChangeTracker
            .Entries<Blog>()
            .Where(e => e.State == EntityState.Modified))
        {
            if (entry.Reference(e => e.Author).CurrentValue == null)
            {
                entry.State = EntityState.Deleted;
            }
        }
    };

搭配 SQL Server 使用 TPT 對應時,數據表之間會設定串聯刪除

追蹤問題 #28532

舊的行為

使用 TPT 策略對應繼承階層時,不論該實體的實際類型為何,基表都必須包含每一個已儲存之實體的數據列。 刪除基表中的數據列應該刪除所有其他數據表中的數據列。 EF Core 會 為此設定串聯刪除

在 EF Core 6.0 中,SQL Server 資料庫提供者中的 Bug 表示尚未建立這些串聯刪除。

新的行為

從EF Core 7.0 開始,現在會針對SQL Server 建立串聯刪除,就像它們一律用於其他資料庫一樣。

原因為何

從基表到 TPT 中子數據表的串聯刪除,允許刪除基表中的數據列來刪除實體。

風險降低

在大部分情況下,這項變更不應該造成任何問題。 不過,當數據表之間設定多個串聯行為時,SQL Server 會非常嚴格。 這表示如果 TPT 對應中的數據表之間有現有的串聯關聯性,SQL Server 可能會產生下列錯誤:

Microsoft.Data.SqlClient.SqlException:DELETE 語句與 REFERENCE 條件約束 “FK_Blogs_人員_OwnerId” 衝突。 資料庫 「Scratch」,數據表 「dbo」 中發生衝突。Blogs“, column 'OwnerId'。 陳述式已經結束。

例如,此模型會建立串聯關聯性的迴圈:

[Table("FeaturedPosts")]
public class FeaturedPost : Post
{
    public int ReferencePostId { get; set; }
    public Post ReferencePost { get; set; } = null!;
}

[Table("Posts")]
public class Post
{
    public int Id { get; set; }
    public string? Title { get; set; }
    public string? Content { get; set; }
}

其中一項必須設定為不要在伺服器上使用串聯刪除。 例如,若要變更明確關聯性:

modelBuilder
    .Entity<FeaturedPost>()
    .HasOne(e => e.ReferencePost)
    .WithMany()
    .OnDelete(DeleteBehavior.ClientCascade);

或變更針對 TPT 對應建立的隱含關聯性:

modelBuilder
    .Entity<FeaturedPost>()
    .HasOne<Post>()
    .WithOne()
    .HasForeignKey<FeaturedPost>(e => e.Id)
    .OnDelete(DeleteBehavior.ClientCascade);

未使用預先寫入記錄時,SQLite 上忙碌/鎖定錯誤的機率較高

舊的行為

舊版 SQLite 提供者會透過較不有效率的技術儲存變更,當數據表鎖定/忙碌且未啟用預先寫入記錄時,可以自動重試。

新的行為

根據預設,EF Core 現在會使用 RETURNING 子句,透過更有效率的技術儲存變更。 不幸的是,這項技術無法在忙碌/鎖定時自動重試。 在多線程應用程式中(例如 Web 應用程式)不使用預先寫入記錄,通常會發生這些錯誤。

原因為何

連結至新方法的簡化和效能改善已足夠重要,因此預設將他們帶到使用者非常重要。 EF Core 所建立的資料庫預設也會啟用預先寫入記錄。 SQLite 小組也建議預設啟用預先寫入記錄。

風險降低

可能的話,您應該在資料庫上啟用預先寫入記錄。 如果您的資料庫是由 EF 所建立,就應該已經是這種情況。 如果沒有,您可以執行下列命令來啟用預先寫入記錄。

PRAGMA journal_mode = 'wal';

如果基於某些原因,您無法啟用預先寫入記錄,則可以在內容組態中插入下列程式代碼,還原為整個應用程式的舊機制:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlite(...)
        .ReplaceService<IUpdateSqlGenerator, SqliteLegacyUpdateSqlGenerator>();

低影響變更

索引鍵屬性可能需要設定提供者值比較子

追蹤問題 #27738

舊的行為

在 EF Core 6.0 中,直接從實體類型屬性取得的索引鍵值,用於儲存變更時索引鍵值的比較。 這會使用這些屬性上設定的任何 自定義值比較子

新的行為

從EF Core 7.0 開始,資料庫值會用於這些比較。 這“只是適用於絕大多數案件”。 不過,如果屬性使用自定義比較子,而且該比較子無法套用至資料庫值,則可能需要「提供者值比較子」,如下所示。

原因為何

各種實體分割和數據表分割可能會導致多個屬性對應至相同的資料庫數據行,反之亦然。 這需要在轉換成將在資料庫中使用的值之後,比較值。

風險降低

設定提供者值比較子。 例如,請考慮使用值物件做為索引鍵的情況,而該索引鍵的比較子會使用不區分大小寫的字串比較:

var blogKeyComparer = new ValueComparer<BlogKey>(
    (l, r) => string.Equals(l.Id, r.Id, StringComparison.OrdinalIgnoreCase),
    v => v.Id.ToUpper().GetHashCode(),
    v => v);

var blogKeyConverter = new ValueConverter<BlogKey, string>(
    v => v.Id,
    v => new BlogKey(v));

modelBuilder.Entity<Blog>()
    .Property(e => e.Id).HasConversion(
        blogKeyConverter, blogKeyComparer);

資料庫值 (strings) 無法直接使用針對 BlogKey 類型定義的比較子。 因此,必須設定不區分大小寫字串比較的提供者比較子:

var caseInsensitiveComparer = new ValueComparer<string>(
    (l, r) => string.Equals(l, r, StringComparison.OrdinalIgnoreCase),
    v => v.ToUpper().GetHashCode(),
    v => v);

var blogKeyComparer = new ValueComparer<BlogKey>(
    (l, r) => string.Equals(l.Id, r.Id, StringComparison.OrdinalIgnoreCase),
    v => v.Id.ToUpper().GetHashCode(),
    v => v);

var blogKeyConverter = new ValueConverter<BlogKey, string>(
    v => v.Id,
    v => new BlogKey(v));

modelBuilder.Entity<Blog>()
    .Property(e => e.Id).HasConversion(
        blogKeyConverter, blogKeyComparer, caseInsensitiveComparer);

檢查條件約束和其他數據表 Facet 現在已在數據表上設定

追蹤問題 #28205

舊的行為

在EF Core 6.0、 HasCheckConstraintHasCommentIsMemoryOptimized 中,直接在實體類型產生器上呼叫 。 例如:

modelBuilder
    .Entity<Blog>()
    .HasCheckConstraint("CK_Blog_TooFewBits", "Id > 1023");

modelBuilder
    .Entity<Blog>()
    .HasComment("It's my table, and I'll delete it if I want to.");

modelBuilder
    .Entity<Blog>()
    .IsMemoryOptimized();

新的行為

從EF Core 7.0 開始,這些方法會改為在數據表產生器上呼叫:

modelBuilder
    .Entity<Blog>()
    .ToTable(b => b.HasCheckConstraint("CK_Blog_TooFewBits", "Id > 1023"));

modelBuilder
    .Entity<Blog>()
    .ToTable(b => b.HasComment("It's my table, and I'll delete it if I want to."));

modelBuilder
    .Entity<Blog>()
    .ToTable(b => b.IsMemoryOptimized());

現有的方法已標示為 Obsolete。 它們目前的行為與新方法相同,但在未來的版本中將會移除。

原因為何

這些 Facet 僅適用於資料表。 它們不會套用至任何對應的檢視、函式或預存程式。

風險降低

使用數據表產生器方法,如上所示。

追蹤問題 #28249

舊的行為

在EF Core 6.0 中,當從追蹤查詢或附加至 來追蹤新實體時,就會修正狀態中相關實體的Deleted巡覽。DbContext

新的行為

從EF Core 7.0 開始,不會固定對實體的 Deleted 導覽。

原因為何

一旦實體標示為 Deleted 與未刪除的實體建立關聯,就很少有意義。

風險降低

在將實體標示為 Deleted之前,先查詢或附加實體,或手動將導覽屬性設定為已刪除的實體。

追蹤問題 #26502

舊的行為

在 EF Core 6.0 中,在使用關係提供者時使用 Azure Cosmos DB FromSqlRaw 擴充方法,或使用 Azure Cosmos DB 提供者時的關係 FromSqlRaw 型擴充方法可能會以無訊息方式失敗。 同樣地,在記憶體內部提供者上使用關係型方法是無訊息的 no-op。

新的行為

從 EF Core 7.0 開始,使用針對不同提供者上一個提供者設計的擴充方法,將會擲回例外狀況。

原因為何

在所有情況下,都必須使用正確的擴充方法,才能正常運作。

風險降低

針對所使用的提供者,使用正確的擴充方法。 如果參考多個提供者,則呼叫擴充方法做為靜態方法。 例如:

var result = CosmosQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToList();

或:

var result = RelationalQueryableExtensions.FromSqlRaw(context.Blogs, "SELECT ...").ToList();

Scaffolded OnConfiguring 不再呼叫 IsConfigured

追蹤問題 #4274

舊的行為

在EF Core 6.0 中 DbContext ,從現有資料庫建構的類型包含對 IsConfigured的呼叫。 例如:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. You can avoid scaffolding the connection string by using the Name= syntax to read it from configuration - see https://go.microsoft.com/fwlink/?linkid=2131148. For more guidance on storing connection strings, see http://go.microsoft.com/fwlink/?LinkId=723263.
        optionsBuilder.UseNpgsql("MySecretConnectionString");
    }
}

新的行為

從 EF Core 7.0 開始,不再包含 對 IsConfigured 的呼叫。

原因為何

在某些情況下,資料庫提供者在 DbContext 內設定的情況非常有限,但前提是尚未設定內容。 相反地,離開OnConfiguring這裡可能會讓包含敏感性資訊的 連接字串 留在程序代碼中,儘管有編譯時期警告。 因此,移除此程式碼的額外安全且更簡潔,這被認為是值得的,特別是考慮到 --no-onconfiguring (.NET CLI) 或 -NoOnConfiguring (Visual Studio 封裝管理員 Console) 旗標可用來防止方法的 Scaffolding,而且如果真的需要,可自定義的OnConfiguring範本就會存在以加回IsConfigured

風險降低

任一:

  • --no-onconfiguring 從現有的資料庫建構時,請使用 (.NET CLI) 或 -NoOnConfiguring (Visual Studio 封裝管理員 Console) 自變數。
  • 自訂 T4 樣本 ,以將呼叫重新新增至 IsConfigured