Share via


EF Core 5.0 的重大變更

下列 API 和行為變更可能會中斷現有應用程式更新至 EF Core 5.0.0。

摘要

重大變更 影響
EF Core 5.0 不支援 .NET Framework
IProperty.GetColumnName() 現已過時
小數點需要有效位數和小數位數
從主體到相依的必要或不可為 Null 導覽具有不同的語意
定義查詢會取代為提供者特定的方法
查詢不會覆寫非 Null 參考導覽
移轉會以不同的方式處理 ToView()
ToTable(null) 會將實體類型標示為未對應至資料表
已從 SQLite NTS 擴充功能移除 HasGeometricDimension 方法
Azure Cosmos DB:分割區索引鍵現在已新增至主鍵
Azure Cosmos DB: id 已將 屬性重新命名為 __id
Azure Cosmos DB:byte[] 現在會儲存為 base64 字串,而不是數位陣列
Azure Cosmos DB:GetPropertyName 和 SetPropertyName 已重新命名
當實體狀態從中斷連結變更為未變更、已更新或刪除時,就會呼叫值產生器
IMigrationsModelDiffer 現在使用 IRelationalModel
辨識子是唯讀的
提供者特定的 EF。InMemory 提供者的函式方法擲回
IndexBuilder.HasName 現已過時
複數器現在已包含用於 Scaffolding 反向工程模型
INavigationBase 會取代某些 API 中的 INavigation 以支援略過導覽
某些查詢與也使用 DistinctGroupBy 不再支援的相互關聯集合
不支援在投影中使用 Queryable 類型的集合

中等影響變更

EF Core 5.0 不支援 .NET Framework

追蹤問題 #15498

舊的行為

EF Core 3.1 以 .NET Standard 2.0 為目標,這是 .NET Framework 所支援。

新的行為

EF Core 5.0 以 .NET Standard 2.1 為目標,而 .NET Framework 不支援此版本。 這表示 EF Core 5.0 無法與 .NET Framework 應用程式搭配使用。

原因為何

這是針對統一到單一 .NET 目標架構之 .NET 小組更廣泛移動的一部分。 如需詳細資訊,請參閱 .NET Standard 的未來。

風險降低

.NET Framework 應用程式可以繼續使用 EF Core 3.1,這是 長期支援 (LTS) 版本 。 或者,應用程式可以更新為使用 .NET Core 3.1 或 .NET 5,這兩者都支援 .NET Standard 2.1。

IProperty.GetColumnName() 現已過時

追蹤問題 #2266

舊的行為

GetColumnName() 傳回屬性所對應之資料行的名稱。

新的行為

GetColumnName() 仍然會傳回屬性所對應之資料行的名稱,但此行為現在模棱兩可,因為 EF Core 5 支援 TPT,同時對應至檢視或函式,而這些對應可以針對相同的屬性使用不同的資料行名稱。

原因為何

我們已將此方法標示為過時,以引導使用者更精確的多載 - GetColumnName(IProperty, StoreObjectIdentifier)

風險降低

如果實體類型只對應至單一資料表,且永遠不會對應至檢視、函式或多個資料表, GetColumnBaseName(IReadOnlyProperty) 則可以在 EF Core 5.0 和 6.0 中使用 來取得資料表名稱。 例如:

var columnName = property.GetColumnBaseName();

在 EF Core 7.0 中,這可以再次取代為新的 GetColumnName ,其行為就像原始的簡單單一資料表對應一樣。

如果實體類型可能對應至檢視、函式或多個資料表, StoreObjectIdentifier 則必須取得 來識別資料表、檢視或函式。 然後,這可以用來取得該存放區物件的資料行名稱。 例如:

var columnName = property.GetColumnName(StoreObjectIdentifier.Table("Users", null)));

小數點需要有效位數和小數位數

追蹤問題 #19293

舊的行為

EF Core 通常不會在 物件上 SqlParameter 設定有效位數和小數位數。 這表示完整有效位數和小數位數已傳送至 SQL Server,此時 SQL Server 會根據資料庫資料行的精確度和小數位數四捨五入。

新的行為

EF Core 現在會使用針對 EF Core 模型中屬性所設定的值,在參數上設定有效位數和小數位數。 這表示現在會在 SqlClient 中四捨五入。 因此,如果設定的精確度和小數位數不符合資料庫有效位數和小數位數,則看到的四捨五入可能會變更。

原因為何

較新的 SQL Server 功能,包括 Always Encrypted,需要完整指定參數 Facet。 此外,SqlClient 已變更為舍入而不是截斷十進位值,因此符合 SQL Server 行為。 這可讓 EF Core 設定這些 Facet,而不需要變更正確設定小數點的行為。

風險降低

使用包含有效位數和小數位數的類型名稱來對應您的十進位屬性。 例如:

public class Blog
{
    public int Id { get; set; }

    [Column(TypeName = "decimal(16, 5)")]
    public decimal Score { get; set; }
}

HasPrecision或在模型建置 API 中使用。 例如:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().Property(e => e.Score).HasPrecision(16, 5);
    }

從主體到相依的必要或不可為 Null 導覽具有不同的語意

追蹤問題 #17286

舊的行為

只有流覽主體可以視需要進行設定。 因此,在 RequiredAttribute 巡覽至相依的 (包含外鍵的實體) 或將它標示為不可為 Null 時,會改為在定義實體類型上建立外鍵。

新的行為

隨著對必要相依專案的新增支援,現在可以視需要將任何參考導覽標示為必要,這表示在外鍵上方顯示的案例中,將會定義在關聯性的另一端,而且不會將屬性標示為必要。

在指定相依端之前呼叫 IsRequired 現在模棱兩可:

modelBuilder.Entity<Blog>()
    .HasOne(b => b.BlogImage)
    .WithOne(i => i.Blog)
    .IsRequired()
    .HasForeignKey<BlogImage>(b => b.BlogForeignKey);

原因為何

需要新的行為,才能支援必要的相依專案( 請參閱 #12100 )。

風險降低

RequiredAttribute 流覽中移除至相依專案,並將它改為放在流覽主體上,或在 中 OnModelCreating 設定關聯性:

modelBuilder.Entity<Blog>()
    .HasOne(b => b.BlogImage)
    .WithOne(i => i.Blog)
    .HasForeignKey<BlogImage>(b => b.BlogForeignKey)
    .IsRequired();

定義查詢會取代為提供者特定的方法

追蹤問題 #18903

舊的行為

實體類型已對應至在核心層級定義查詢。 每當實體類型在實體類型的查詢根目錄中使用時,都由任何提供者的定義查詢所取代。

新的行為

定義查詢的 API 已被取代。 引進了新的提供者特定 API。

原因為何

雖然定義查詢會在查詢中使用查詢根目錄時實作為取代查詢,但有一些問題:

  • 如果定義查詢是在 方法中使用 Select 來投影實體類型 new { ... } ,則識別為實體需要額外的工作,並使其與 EF Core 在查詢中處理名義類型的方式不一致。
  • 對於關聯式提供者 FromSql ,仍然需要以 LINQ 運算式形式傳遞 SQL 字串。

最初將查詢定義為用戶端檢視,以搭配無索引鍵實體的記憶體內部提供者使用(類似于關係資料庫中的資料庫檢視)。 這類定義可讓您輕鬆地針對記憶體內部資料庫測試應用程式。 之後,他們變得廣泛適用,這是有用的,但帶來了不一致和難以理解的行為。 因此,我們決定簡化概念。 我們根據 LINQ 來定義記憶體內部提供者專屬的查詢,並以不同的方式加以處理。 如需詳細資訊,請參閱此問題

風險降低

對於關聯式提供者,請使用 ToSqlQuery 中的 OnModelCreating 方法,並傳入 SQL 字串以用於實體類型。 針對記憶體內部提供者,請使用 ToInMemoryQuery 中的 OnModelCreating 方法,並傳入 LINQ 查詢以用於實體類型。

查詢不會覆寫非 Null 參考導覽

追蹤問題 #2693

舊的行為

在 EF Core 3.1 中,不論索引鍵值是否相符,都有時候會由來自資料庫的實體實例覆寫急切地初始化為非 Null 值的參考導覽。 不過,在其他情況下,EF Core 3.1 會執行相反動作,並保留現有的非 Null 值。

新的行為

從 EF Core 5.0 開始,從查詢傳回的實例永遠不會覆寫非 Null 參考導覽。

請注意,仍然支援集合 導覽至空集合的急切初始化

原因為何

將參考導覽屬性初始化為「空白」實體實例會導致模棱兩可的狀態。 例如:

public class Blog
{
     public int Id { get; set; }
     public Author Author { get; set; ) = new Author();
}

一般而言,部落格和作者的查詢會先建立 Blog 實例,然後根據從資料庫傳回的資料來設定適當的 Author 實例。 不過,在此情況下,每個 Blog.Author 屬性都已經初始化為空 Author 的 。 除了 EF Core 無法知道此實例為「空白」。 因此,覆寫此實例可能會以無訊息方式擲回有效的 Author 。 因此,EF Core 5.0 現在一致不會覆寫已初始化的流覽。

在大部分情況下,這種新行為也與 EF6 的行為一致,不過在調查時,我們也發現 EF6 中有些不一致的情況。

風險降低

如果遇到此中斷,則修正是停止急切地初始化參考導覽屬性。

移轉會以不同的方式處理 ToView()

追蹤問題 #2725

舊的行為

呼叫 ToView(string) 時,移轉除了將實體類型對應至檢視之外,也會忽略實體類型。

新的行為

現在 ToView(string) ,除了將實體類型對應至檢視之外,也會將實體類型標示為未對應至資料表。 這會導致升級至 EF Core 5 之後的第一次移轉,嘗試卸載此實體類型的預設資料表,因為它不再被忽略。

原因為何

EF Core 現在允許同時對應至資料表和檢視的實體類型,因此 ToView 不再是移轉應該忽略的有效指標。

風險降低

使用下列程式碼將對應的資料表標示為移轉排除:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().ToTable("UserView", t => t.ExcludeFromMigrations());
}

ToTable(null) 會將實體類型標示為未對應至資料表

追蹤問題 #21172

舊的行為

ToTable(null) 會將資料表名稱重設為預設值。

新的行為

ToTable(null) 現在會將實體類型標示為未對應至任何資料表。

原因為何

EF Core 現在允許同時對應至資料表和檢視的實體類型,因此 ToTable(null) 用來指出它未對應至任何資料表。

風險降低

如果資料表名稱未對應至檢視或 DbFunction,請使用下列程式碼,將資料表名稱重設為預設值:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().Metadata.RemoveAnnotation(RelationalAnnotationNames.TableName);
}

低影響變更

已從 SQLite NTS 擴充功能移除 HasGeometricDimension 方法

追蹤問題 #14257

舊的行為

HasGeometricDimension 可用來在幾何資料行上啟用其他維度 (Z 和 M)。 不過,它只會影響資料庫建立。 不需要指定它來查詢具有其他維度的值。 插入或更新具有其他維度的值時,它也無法正確運作( 請參閱 #14257 )。

新的行為

若要使用其他維度啟用插入和更新幾何值 (Z 和 M),必須將維度指定為數據行類型名稱的一部分。 此 API 更符合 SpatiaLite 的 AddGeometryColumn 函式的基礎行為。

原因為何

在指定資料行類型中的維度之後,使用 HasGeometricDimension 是不必要的且多餘的,因此我們完全移除了 HasGeometricDimension。

風險降低

使用 HasColumnType 來指定維度:

modelBuilder.Entity<GeoEntity>(
    x =>
    {
        // Allow any GEOMETRY value with optional Z and M values
        x.Property(e => e.Geometry).HasColumnType("GEOMETRYZM");

        // Allow only POINT values with an optional Z value
        x.Property(e => e.Point).HasColumnType("POINTZ");
    });

Azure Cosmos DB:分割區索引鍵現在已新增至主鍵

追蹤問題 #15289

舊的行為

資料分割索引鍵屬性只會新增至包含 id 的替代索引鍵。

新的行為

分割區索引鍵屬性現在也會依照慣例新增至主鍵。

原因為何

這項變更可讓模型更符合 Azure Cosmos DB 語意,並改善和某些查詢的 Find 效能。

風險降低

若要防止將資料分割索引鍵屬性新增至主鍵,請在 中 OnModelCreating 設定它。

modelBuilder.Entity<Blog>()
    .HasKey(b => b.Id);

Azure Cosmos DB: id 已將 屬性重新命名為 __id

追蹤問題 #17751

舊的行為

對應至 JSON 屬性的 id 陰影屬性也命名為 id

新的行為

慣例所建立的陰影屬性現在命名為 __id

原因為何

這項變更使得屬性與實體類型上現有的屬性發生衝突的可能性 id 較小。

風險降低

若要回到 3.x 行為,請在 中 OnModelCreating 設定 id 屬性。

modelBuilder.Entity<Blog>()
    .Property<string>("id")
    .ToJsonProperty("id");

Azure Cosmos DB:byte[] 現在會儲存為 base64 字串,而不是數位陣列

追蹤問題 #17306

舊的行為

byte[] 類型的屬性已儲存為數字陣列。

新的行為

byte[] 類型的屬性現在會儲存為 base64 字串。

原因為何

這個 byte[] 標記法與預期更相符,而且是主要 JSON 序列化程式庫的預設行為。

風險降低

儲存為數字陣列的現有資料仍會正確查詢,但目前不支援變更插入行為的方式。 如果此限制封鎖您的案例,請對此問題加上批註

Azure Cosmos DB:GetPropertyName 和 SetPropertyName 已重新命名

追蹤問題 #17874

舊的行為

先前已呼叫 GetPropertyName 擴充方法, SetPropertyName

新的行為

已移除舊的 API,並新增了新方法: GetJsonPropertyNameSetJsonPropertyName

原因為何

這項變更會移除這些方法設定的模棱兩可。

風險降低

使用新的 API。

當實體狀態從中斷連結變更為未變更、已更新或刪除時,就會呼叫值產生器

追蹤問題 #15289

舊的行為

只有在實體狀態變更為 [已新增] 時,才會呼叫值產生器。

新的行為

當實體狀態從 [中斷連結] 變更為 [未變更]、[已更新] 或 [刪除],且屬性包含預設值時,現在會呼叫值產生器。

原因為何

這項變更是必要的,才能改善未保存到資料存放區之屬性的體驗,並在用戶端上一律產生其值。

風險降低

若要防止呼叫值產生器,請在變更狀態之前,將非預設值指派給 屬性。

IMigrationsModelDiffer 現在使用 IRelationalModel

追蹤問題 #20305

舊的行為

IMigrationsModelDiffer API 已使用 IModel 定義。

新的行為

IMigrationsModelDiffer API 現在使用 IRelationalModel 。 不過,模型快照集仍然包含 IModel ,因為此程式碼是應用程式的一部分,而 Entity Framework 無法在不進行更大的重大變更的情況下加以變更。

原因為何

IRelationalModel 是新加入的資料庫架構標記法。 使用它來尋找差異更快速且更精確。

風險降低

使用下列程式碼,從 中比較模型 snapshot 與 來自 context 的模型:

var dependencies = context.GetService<ProviderConventionSetBuilderDependencies>();
var relationalDependencies = context.GetService<RelationalConventionSetBuilderDependencies>();

var typeMappingConvention = new TypeMappingConvention(dependencies);
typeMappingConvention.ProcessModelFinalizing(((IConventionModel)modelSnapshot.Model).Builder, null);

var relationalModelConvention = new RelationalModelConvention(dependencies, relationalDependencies);
var sourceModel = relationalModelConvention.ProcessModelFinalized(snapshot.Model);

var modelDiffer = context.GetService<IMigrationsModelDiffer>();
var hasDifferences = modelDiffer.HasDifferences(
    ((IMutableModel)sourceModel).FinalizeModel().GetRelationalModel(),
    context.Model.GetRelationalModel());

我們打算在 6.0 中改善此體驗( 請參閱 #22031

辨識子是唯讀的

追蹤問題 #21154

舊的行為

在呼叫之前,可以變更歧視性值 SaveChanges

新的行為

在上述案例中,將會擲回例外狀況。

原因為何

EF 不會預期實體類型在仍在追蹤時變更,因此變更歧視性值會使內容處於不一致的狀態,這可能會導致非預期的行為。

風險降低

如果變更歧視性值是必要的,而且內容會在呼叫 SaveChanges 之後立即處置,則可以將歧視性設為可變動:

modelBuilder.Entity<BaseEntity>()
    .Property<string>("Discriminator")
    .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);

提供者特定的 EF。InMemory 提供者的函式方法擲回

追蹤問題 #20294

舊的行為

提供者特定的 EF。函式方法包含用戶端執行的實作,可讓它們在 InMemory 提供者上執行。 例如, EF.Functions.DateDiffDay 是 Sql Server 特定方法,在 InMemory 提供者上運作。

新的行為

提供者特定的方法已更新,以在其方法主體中擲回例外狀況,以封鎖在用戶端評估它們。

原因為何

提供者特定的方法會對應至資料庫函式。 對應資料庫函式所完成的計算不一定會在 LINQ 的用戶端上複寫。 在用戶端上執行相同方法時,它可能會導致伺服器的結果不同。 由於這些方法會用於 LINQ 來轉譯為特定的資料庫函式,因此不需要在用戶端上進行評估。 由於 InMemory 提供者是不同的 資料庫 ,因此此提供者無法使用這些方法。 嘗試針對 InMemory 提供者或未轉譯這些方法的任何其他提供者執行它們,會擲回例外狀況。

風險降低

由於無法正確模擬資料庫函式的行為,因此您應該針對與生產環境中相同的資料庫測試包含它們的查詢。

IndexBuilder.HasName 現已過時

追蹤問題 #21089

舊的行為

先前,只有一個索引可以透過一組指定的屬性來定義。 索引的資料庫名稱是使用 IndexBuilder.HasName 設定的。

新的行為

在相同的集合或屬性上,現在允許多個索引。 這些索引現在會以模型中的名稱來辨別。 依照慣例,模型名稱會當做資料庫名稱使用;不過,也可以使用 HasDatabaseName 獨立設定它。

原因為何

未來,我們想要在同一組屬性上啟用具有不同定序的遞增和遞減索引或索引。 這項變更將我們移至該方向的另一步。

風險降低

先前呼叫 IndexBuilder.HasName 的任何程式碼都應該更新為改為呼叫 HasDatabaseName。

如果您的專案包含 EF Core 2.0.0 版之前產生的移轉,您可以藉由新增 #pragma warning disable 612, 618 來安全地忽略這些檔案中的警告並加以隱藏。

複數器現在已包含用於 Scaffolding 反向工程模型

追蹤問題 #11160

舊的行為

之前,您必須安裝個別的複數化程式套件,以便在反向工程資料庫架構來建立 DbCoNtext 和實體類型時,將 DbSet 和集合導覽名稱和單一化資料表名稱複寫。

新的行為

EF Core 現在包含使用 Humanizer 程式庫的 複數化程式。 這是 Visual Studio 用來建議變數名稱的相同程式庫。

原因為何

針對集合屬性使用複數形式的文字,以及類型與參考屬性的單數形式,在 .NET 中是慣用的。

風險降低

若要停用複數化程式,請使用 --no-pluralize 上的 dotnet ef dbcontext scaffold 選項或 -NoPluralize 上的 Scaffold-DbContext 參數。

INavigationBase 會取代某些 API 中的 INavigation 以支援略過導覽

追蹤問題 #2568

舊的行為

在 5.0 之前的 EF Core 只支援一種巡覽屬性形式,由 INavigation 介面表示。

新的行為

EF Core 5.0 引進使用「略過導覽」的多對多關聯性。 這些是由 介面所代表 ISkipNavigation ,大部分的功能 INavigation 都已向下推送至通用基底介面: INavigationBase

原因為何

一般和略過導覽之間的大部分功能都相同。 不過,略過導覽與外鍵的關聯性與一般導覽不同,因為所涉及的 FK 不會直接在關聯性任一端,而是在聯結實體中。

風險降低

在許多情況下,應用程式可以切換至使用新的基底介面,而沒有任何其他變更。 不過,在巡覽用來存取外鍵屬性的情況下,應用程式程式碼應限制為只有一般導覽,或更新為針對一般和略過流覽執行適當的動作。

某些查詢與也使用 DistinctGroupBy 不再支援的相互關聯集合

追蹤問題 #15873

舊行為

先前,涉及相互關聯集合的 GroupBy 查詢後面接著 ,以及一些使用 Distinct 我們允許執行的查詢。

GroupBy 範例:

context.Parents
    .Select(p => p.Children
        .GroupBy(c => c.School)
        .Select(g => g.Key))

Distinct 範例 - 特別是 Distinct 查詢內部集合投影不包含主鍵的位置:

context.Parents
    .Select(p => p.Children
        .Select(c => c.School)
        .Distinct())

如果內部集合包含任何重複專案,這些查詢可能會傳回不正確的結果,但如果內部集合中的所有元素都是唯一的,則正確運作。

新行為

不再支援這些查詢。 擲回例外狀況,指出我們沒有足夠的資訊可正確建置結果。

原因為何

針對相互關聯的集合案例,我們需要知道實體的主鍵,才能將集合實體指派給正確的父系。 當內部集合不使用 GroupByDistinct 時,遺漏的主鍵可以直接新增至投影。 不過,如果 GroupByDistinct 和 無法完成,因為它會變更 或 Distinct 作業的結果 GroupBy

風險降低

請重寫查詢,不要在內部集合上使用 GroupByDistinct 作業,並改為在用戶端上執行這些作業。

context.Parents
    .Select(p => p.Children.Select(c => c.School))
    .ToList()
    .Select(x => x.GroupBy(c => c).Select(g => g.Key))
context.Parents
    .Select(p => p.Children.Select(c => c.School))
    .ToList()
    .Select(x => x.Distinct())

不支援在投影中使用 Queryable 類型的集合

追蹤問題 #16314

舊行為

先前,在某些情況下,可以使用投影內可查詢型別的集合,例如作為建構函式的 List<T> 引數:

context.Blogs
    .Select(b => new List<Post>(context.Posts.Where(p => p.BlogId == b.Id)))

新行為

不再支援這些查詢。 擲回例外狀況,指出我們無法建立可查詢型別的物件,並建議如何修正此問題。

原因為何

我們無法具體化可查詢型別的物件,因此會改用 List<T> 類型自動建立這些物件。 這通常會因為類型不符而造成例外狀況,這並不十分清楚,而且對某些使用者可能會感到驚訝。 我們決定辨識模式,並擲回更有意義的例外狀況。

風險降低

在投影中的 Queryable 物件後面新增 ToList() 呼叫:

context.Blogs.Select(b => context.Posts.Where(p => p.BlogId == b.Id).ToList())