共用方式為


EF Core 8 的重大變更 (EF8)

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

目標 Framework

EF Core 8 的目標是 .NET 8。 以舊版 .NET、.NET Core 和 .NET Framework 版本為目標的應用程式必須更新為以 .NET 8 為目標。

摘要

重大變更 影響
Contains 在 LINQ 查詢中,可能會停止在舊版 SQL Server 上運作
JSON 中的列舉預設會儲存為 ints,而不是字串
SQL Server datetime 現在 Scaffold 至 .NET DateOnlyTimeOnly
具有資料庫產生值的布爾數據行不再以可為 Null 的形式 Scaffold
SQLite Math 方法現在會轉譯為 SQL
ITypeBase 會取代某些 API 中的 IEntityType
ValueGenerator 運算式必須使用公用 API
ExcludeFromMigrations 不再排除 TPC 階層中的其他數據表
非陰影整數索引鍵會保存至 Cosmos 檔
關係型模型會在編譯的模型中產生
Scaffolding 可能會產生不同的導覽名稱
歧視性現在具有最大長度
SQL Server 索引鍵值不區分大小寫地比較
多個 AddDbContext 呼叫會以不同的順序套用
EntityTypeAttributeConventionBase 取代為 TypeAttributeConventionBase

高度影響變更

Contains 在 LINQ 查詢中,可能會停止在舊版 SQL Server 上運作

追蹤問題 #13617

舊的行為

先前,當運算符在 LINQ 查詢中使用參數化值清單時 Contains ,EF 會產生效率不佳但適用於所有 SQL Server 版本的 SQL。

新的行為

從EF Core 8.0 開始,EF 現在會產生更有效率的 SQL,但在 SQL Server 2014 和以下版本不支援。

請注意,較新的 SQL Server 版本可能會設定為較舊的 相容性層級,也使其與新的 SQL 不相容。 這也可能發生於從先前內部部署 SQL Server 實例移轉的 Azure SQL 資料庫,並承載舊的相容性層級。

原因為何

EF Core 產生的先前 SQL,用於 Contains 將參數化值插入為 SQL 中的常數。 例如,下列 LINQ 查詢:

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

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

...會轉譯成下列 SQL:

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

在 SQL 中插入常數值會產生許多效能問題、使查詢計劃快取失敗,以及造成其他查詢不需要的收回。 新的EF Core 8.0 轉譯會使用 SQL Server OPENJSON 函式,改為將值傳送為 JSON 陣列。 這可解決先前技術固有的效能問題:不過,SQL Server 2014 和以下版本無法使用函 OPENJSON 式。

如需這項變更的詳細資訊, 請參閱此部落格文章

風險降低

如果您的資料庫是 SQL Server 2016 (13.x) 或更新版本,或者如果您使用 Azure SQL,請透過下列命令檢查您資料庫的已設定相容性層級:

SELECT name, compatibility_level FROM sys.databases;

如果相容性層級低於 130 (SQL Server 2016),請考慮將其修改為較新的值()。

否則,如果您的資料庫版本真的比 SQL Server 2016 還舊,或設定為舊相容性層級,因為某些原因而無法變更,請將 EF Core 設定為還原為較舊且效率較低的 SQL,如下所示:

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

JSON 中的列舉預設會儲存為 ints,而不是字串

追蹤問題 #13617

舊的行為

在EF7中, 對應至 JSON 的列舉預設會儲存為 JSON 檔中的字串值。

新的行為

從EF Core 8.0 開始,EF 現在預設會將列舉對應至 JSON 檔中的整數值。

原因為何

根據預設,EF 一律會將列舉對應至關係資料庫中的數值數據行。 因為 EF 支援來自 JSON 的值與資料行和參數值互動的查詢,因此 JSON 中的值必須符合非 JSON 數據行中的值。

風險降低

若要繼續使用字串,請使用轉換來設定列舉屬性。 例如:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().Property(e => e.Status).HasConversion<string>();
}

或者,針對列舉類型的所有屬性::

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Properties<StatusEnum>().HaveConversion<string>();
}

中度影響變更

SQL Server datetime 現在 Scaffold 至 .NET DateOnlyTimeOnly

追蹤問題 #24507

舊的行為

先前,使用 或 time 數據行建構 SQL Server 資料庫date時,EF 會產生具有 類型和 DateTimeTimeSpan實體屬性。

新的行為

從 EF Core 8.0 開始,datetime 會以 和 TimeOnly的形式建構DateOnly

原因為何

DateOnlyTimeOnly 是在 .NET 6.0 中引進,而且非常適合用來對應資料庫日期和時間類型。 DateTime 值得注意的是,包含一個未使用的時間元件,而且在對應至 date時可能會造成混淆,而且 TimeSpan 代表時間間隔,可能包括天,而不是事件發生的一天時間。 使用新類型可防止 Bug 和混淆,並提供意圖的明確性。

風險降低

這項變更只會影響定期將其資料庫重新建構為 EF 程式代碼模型的使用者(“database-first”流程)。

建議您修改程式代碼以使用新 Scaffolded DateOnlyTimeOnly 類型,以回應這項變更。 不過,如果不可能,您可以編輯 Scaffolding 範本以還原為先前的對應。 若要這樣做,請設定此頁面所述的範本。 然後,編輯 EntityType.t4 檔案、尋找產生實體屬性的位置(搜尋 property.ClrType),並將程式代碼變更為下列內容:

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

具有資料庫產生值的布爾數據行不再以可為 Null 的形式 Scaffold

追蹤問題 #15070

舊的行為

先前,具有資料庫默認條件約束的非 bool 可為 Null 數據行會建構為可為 bool? Null 的屬性。

新的行為

從EF Core 8.0 開始,不可為 Null 的數據行一律會建構為不可為 Null bool 的屬性。

原因為何

bool如果這個值為 ,則屬性不會將其值false傳送至資料庫,這是CLR預設值。 如果資料庫具有 資料行的預設值,則即使 屬性的值 truefalse,資料庫中的值最終還是會是 true。 不過,在 EF8 中,用來判斷屬性是否有值可變更的 sentinel。 這會自動針對 bool 具有資料庫產生值 true的屬性執行,這表示不再需要將屬性 Scaffold 設為可為 Null。

風險降低

這項變更只會影響定期將其資料庫重新建構為 EF 程式代碼模型的使用者(“database-first”流程)。

建議您修改程式代碼以使用不可為 Null 的 bool 屬性來回應這項變更。 不過,如果不可能,您可以編輯 Scaffolding 範本以還原為先前的對應。 若要這樣做,請設定此頁面所述的範本。 然後,編輯 EntityType.t4 檔案、尋找產生實體屬性的位置(搜尋 property.ClrType),並將程式代碼變更為下列內容:

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

低度影響變更

SQLite Math 方法現在會轉譯為 SQL

追蹤問題 #18843

舊行為

先前只會將 上的 Math Abs、Max、Min 和 Round 方法轉譯為 SQL。 如果所有其他成員出現在查詢的最終 Select 運算式中,則會在用戶端上進行評估。

新的行為

在 EF Core 8.0 中,具有對應 SQLite 數學函式的所有Math方法都會轉譯為 SQL。

這些數學函式已在我們預設提供的原生 SQLite 連結庫中啟用(透過我們對 SQLitePCLRaw.bundle_e_sqlite3 NuGet 套件的相依性)。 它們也已在SQLitePCLRaw.bundle_e_sqlcipher所提供的連結庫中啟用。 如果您使用其中一個連結庫,您的應用程式不應該受到這項變更的影響。

不過,有一個機會,透過其他方式包含原生 SQLite 連結庫的應用程式可能無法啟用數學函式。 在這些情況下, Math 方法會轉譯為 SQL,並在執行時不會發生 這類函式 錯誤。

原因為何

SQLite 已在 3.35.0 版中新增內建數學函式。 雖然預設會停用它們,但它們已變得很普遍,因此我們決定在 EF Core SQLite 提供者中為它們提供默認翻譯。

我們也與 SQLitePCLRaw 專案上的 Eric Sink 共同作業,以在該專案中提供的所有原生 SQLite 連結庫中啟用數學函式。

風險降低

修正斷點最簡單的方法是盡可能啟用數學函式是原生 SQLite 連結庫,方法是指定 編譯時間選項SQLITE_ENABLE_MATH_FUNCTIONS

如果您不控制原生連結庫的編譯,您也可以使用 Microsoft.Data.Sqlite API 在運行時間自行建立函式,藉此修正中斷。

sqliteConnection
    .CreateFunction<double, double, double>(
        "pow",
        Math.Pow,
        isDeterministic: true);

或者,您可以將 Select 運算式分割成以 分隔的 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 會取代某些 API 中的 IEntityType

追蹤問題 #13947

舊的行為

先前,所有對應的結構類型都是實體類型。

新的行為

在 EF8 中引進複雜類型時,先前使用的IEntityTypeITypeBase一些 API,讓 API 可以搭配實體或複雜類型使用。 這包括:

  • IProperty.DeclaringEntityType 現在已過時, IProperty.DeclaringType 應該改用 。
  • IEntityTypeIgnoredConvention 現在已過時, ITypeIgnoredConvention 應該改用 。
  • IValueGeneratorSelector.Select 現在接受 ITypeBase 的 ,可能是 ,但不需要是 IEntityType

原因為何

在 EF8 中引進複雜型別後,這些 API 可以搭配 IEntityTypeIComplexType使用。

風險降低

舊的 API 已經過時,但直到 EF10 才會移除。 程式代碼應該更新為使用新的 API ASAP。

ValueConverter 和 ValueComparer 運算式必須針對編譯的模型使用公用 API

追蹤問題 #24896

舊的行為

先前, ValueConverterValueComparer 定義未包含在編譯的模型中,因此可以包含任意程序代碼。

新的行為

EF 現在會從 ValueConverterValueComparer 物件擷取表達式,並在編譯的模型中包含這些 C#。 這表示這些表達式只能使用公用 API。

原因為何

EF 小組會逐漸將更多建構移至已編譯的模型,以支持未來搭配 AOT 使用 EF Core。

風險降低

將比較子使用的 API 設為公用。 例如,請考慮這個簡單的轉換器:

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
}

若要在編譯的模型中搭配EF8使用此轉換器, ConvertToString 必須將 和 ConvertToBytes 方法公開。 例如:

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 不再排除 TPC 階層中的其他數據表

追蹤問題 #30079

舊的行為

先前,在 ExcludeFromMigrations TPC 階層中的數據表上使用 ,也會排除階層中的其他數據表。

新的行為

從 EF Core 8.0 開始, ExcludeFromMigrations 不會影響其他數據表。

原因為何

舊行為是錯誤,並防止移轉用來管理跨專案的階層。

風險降低

ExcludeFromMigrations明確在應排除的任何其他資料表上使用 。

非陰影整數索引鍵會保存至 Cosmos 檔

追蹤問題 #31664

舊的行為

先前,符合準則為合成索引鍵屬性的非陰影整數屬性不會保存在 JSON 檔中,而是在出路時重新合成。

新的行為

從EF Core 8.0 開始,這些屬性現在會保存。

原因為何

舊行為是錯誤,並已防止符合合成索引鍵準則的屬性保存至 Cosmos。

風險降低

如果不應該保存屬性的值,請從模型 中排除屬性。 此外,您可以將 AppContext 參數設定 Microsoft.EntityFrameworkCore.Issue31664true來完全停用此行為,如需詳細資訊,請參閱 連結庫取用者的 AppContext。

AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue31664", isEnabled: true);

關係型模型會在編譯的模型中產生

追蹤問題 #24896

舊的行為

先前,關係型模型是在運行時間計算,即使使用編譯的模型也一樣。

新的行為

從 EF Core 8.0 開始,關係型模型是所產生編譯模型的一部分。 不過,對於特別大型的模型,產生的檔案可能無法編譯。

原因為何

這樣做是為了進一步改善啟動時間。

風險降低

編輯產生的 *ModelBuilder.cs 檔案,並移除該行 AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel()); 以及 方法 CreateRelationalModel()

Scaffolding 可能會產生不同的導覽名稱

追蹤問題 #27832

舊的行為

先前從現有資料庫建構 DbContext 和 實體類型時,關聯性的導覽名稱有時衍生自多個外鍵數據行名稱的通用前置詞。

新的行為

從EF Core 8.0 開始,複合外鍵中數據行名稱的常見前置詞不再用來產生導覽名稱。

原因為何

這是一個模糊的命名規則,有時會產生非常糟糕的名稱,例如、 SStudent_或甚至只是 _。 如果沒有此規則,系統就不會再產生奇怪的名稱,而且流覽的命名慣例也會變得更簡單,因此更容易瞭解及預測將產生的名稱。

風險降低

EF Core Power Tools 可以選擇以舊的方式持續產生導覽。 或者,使用 T4 範本可以完全自定義產生的程式代碼。 這可用來範例 Scaffolding 關聯性的外鍵屬性,並使用任何適合您程式代碼的規則來產生您需要的導覽名稱。

歧視性現在具有最大長度

追蹤問題 #10691

舊的行為

先前,針對 TPH 繼承對應建立的歧視性數據行是在 SQL Server/Azure SQL 上設定為 nvarchar(max) ,或在其他資料庫上設定對等的未繫結字串類型。

新的行為

從 EF Core 8.0 開始,會建立具有最大長度的歧視性數據行,其長度上限涵蓋所有已知的歧視性值。 EF 會產生移轉以進行這項變更。 不過,如果歧視性數據行以某種方式限制,例如,作為索引的一部分,則 AlterColumn 移轉所建立的 可能會失敗。

原因為何

nvarchar(max) 當已知所有可能值的長度時,數據行沒有效率且不必要。

風險降低

資料行大小可以明確取消系結:

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

SQL Server 索引鍵值不區分大小寫地比較

追蹤問題 #27526

舊的行為

先前,使用 SQL Server/Azure SQL 資料庫提供者來追蹤具有字串索引鍵的實體時,會使用預設 .NET 區分大小寫的序數比較子來比較索引鍵值。

新的行為

從EF Core 8.0 開始,SQL Server/Azure SQL 字串索引鍵值會使用預設的 .NET 不區分大小寫序數比較子進行比較。

原因為何

根據預設,SQL Server 在比較外鍵值與主體索引鍵值時,會使用不區分大小寫的比較。 這表示當 EF 使用區分大小寫的比較時,它可能不會在應該時將外鍵連接到主體密鑰。

風險降低

藉由設定自定義 ValueComparer,即可使用區分大小寫的比較。 例如:

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

多個 AddDbContext 呼叫會以不同的順序套用

追蹤問題 #32518

舊的行為

先前,當多次呼叫 AddDbContextAddDbContextPoolAddDbContextFactory AddPooledDbContextFactor 時,使用相同內容類型但發生衝突的組態時,第一個會贏。

新的行為

從 EF Core 8.0 開始,最後一個呼叫的組態優先。

原因為何

這已變更為與可用來在方法之前或之後Add*新增組態的新方法ConfigureDbContext一致。

風險降低

反轉呼叫的順序 Add*

EntityTypeAttributeConventionBase 取代為 TypeAttributeConventionBase

新的行為

在 EF Core 8.0 EntityTypeAttributeConventionBase 中,已重新命名為 TypeAttributeConventionBase

原因為何

TypeAttributeConventionBase 表示功能更好,因為它現在可用於複雜類型和實體類型。

風險降低

將使用量取代 EntityTypeAttributeConventionBaseTypeAttributeConventionBase