EF Core 2.0 中的新功能New features in EF Core 2.0

.NET Standard 2.0.NET Standard 2.0

EF Core 現在的目標是 .NET Standard 2.0,表示它可以處理 .NET Core 2.0、.NET Framework 4.6.1 以及其他實作 .NET Standard 2.0 的程式庫。EF Core now targets .NET Standard 2.0, which means it can work with .NET Core 2.0, .NET Framework 4.6.1, and other libraries that implement .NET Standard 2.0. 如需所支援實作的詳細資料,請參閱支援的 .NET 實作See Supported .NET Implementations for more details on what is supported.


資料表分割Table splitting

現在可以將兩個或多個實體類型對應至相同的資料表;其中,將會共用主要索引鍵資料行,而且每個資料列都會對應至兩個或多個實體。It is now possible to map two or more entity types to the same table where the primary key column(s) will be shared and each row will correspond to two or more entities.

若要使用資料表分割,必須在共用資料表的所有實體類型之間設定識別關聯性 (其中外部索引鍵屬性形成主索引鍵):To use table splitting an identifying relationship (where foreign key properties form the primary key) must be configured between all of the entity types sharing the table:

    .HasOne(e => e.Details).WithOne(e => e.Product)
    .HasForeignKey<ProductDetails>(e => e.Id);

擁有的類型Owned types

擁有的實體類型可以與另一個擁有的實體類型共用相同的 CLR 類型,但因為只有根據 CLR 類型並無法識別它,所以必須從另一個實體類型進行導覽。An owned entity type can share the same CLR type with another owned entity type, but since it cannot be identified just by the CLR type there must be a navigation to it from another entity type. 包含定義導覽的實體是擁有者。The entity containing the defining navigation is the owner. 查詢擁有者時,預設會包含擁有的類型。When querying the owner the owned types will be included by default.

依照慣例,將為擁有的類型建立陰影主索引鍵,而且會使用資料表分割將它對應至與擁有者相同的資料表。By convention a shadow primary key will be created for the owned type and it will be mapped to the same table as the owner by using table splitting. 這允許使用擁有的類型,其類似在 EF6 中如何使用複雜類型:This allows to use owned types similarly to how complex types are used in EF6:

modelBuilder.Entity<Order>().OwnsOne(p => p.OrderDetails, cb =>
        cb.OwnsOne(c => c.BillingAddress);
        cb.OwnsOne(c => c.ShippingAddress);

public class Order
    public int Id { get; set; }
    public OrderDetails OrderDetails { get; set; }

public class OrderDetails
    public StreetAddress BillingAddress { get; set; }
    public StreetAddress ShippingAddress { get; set; }

public class StreetAddress
    public string Street { get; set; }
    public string City { get; set; }

如需這項功能的詳細資訊,請閱讀擁有的實體類型一節。Read the section on owned entity types for more information on this feature.

模型層級查詢篩選Model-level query filters

EF Core 2.0 包含我們稱為「模型層級查詢篩選」的新功能。EF Core 2.0 includes a new feature we call Model-level query filters. 此功能允許直接在中繼資料模型 (通常是在 OnModelCreating 中) 的「實體類型」上定義 LINQ 查詢述詞 (布林運算式通常會傳遞至 LINQ Where 查詢運算子)。This feature allows LINQ query predicates (a boolean expression typically passed to the LINQ Where query operator) to be defined directly on Entity Types in the metadata model (usually in OnModelCreating). 這類篩選會自動套用至任何涉及這些「實體類型」(包含間接參考的「實體類型」) 的 LINQ 查詢 (例如,使用 Include 或直接導覽屬性參考)。Such filters are automatically applied to any LINQ queries involving those Entity Types, including Entity Types referenced indirectly, such as through the use of Include or direct navigation property references. 此功能的一些常見應用如下:Some common applications of this feature are:

  • 虛刪除 -「實體類型」定義 IsDeleted 屬性。Soft delete - An Entity Types defines an IsDeleted property.
  • 多租用戶 -「實體類型」定義 TenantId 屬性。Multi-tenancy - An Entity Type defines a TenantId property.

以下簡單範例示範上述兩個案例的功能:Here is a simple example demonstrating the feature for the two scenarios listed above:

public class BloggingContext : DbContext
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    public int TenantId { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
            p => !p.IsDeleted
            && p.TenantId == this.TenantId );

我們會定義模型層級篩選,以實作 Post 實體類型執行個體的多租用戶和虛刪除。We define a model-level filter that implements multi-tenancy and soft-delete for instances of the Post Entity Type. 請注意如何使用 DbContext 執行個體層級屬性:TenantIdNote the use of a DbContext instance level property: TenantId. 模型層級篩選將使用正確內容執行個體中的值。Model-level filters will use the value from the correct context instance. 也就是執行查詢的項目。I.e. the one that is executing the query.

可能會使用 IgnoreQueryFilters() 運算子停用個別 LINQ 查詢的篩選。Filters may be disabled for individual LINQ queries using the IgnoreQueryFilters() operator.


  • 不允許導覽參考。Navigation references are not allowed. 此功能可能是根據意見所新增。This feature may be added based on feedback.
  • 篩選只能定義於階層的 root 實體類型上。Filters can only be defined on the root Entity Type of a hierarchy.

資料庫純量函式對應Database scalar function mapping

EF Core 2.0 包含 Paul Middleton 的重要比重,這會啟用資料庫純量函式與方法虛設常式的對應,以將它們用於 LINQ 查詢並轉譯為 SQL。EF Core 2.0 includes an important contribution from Paul Middleton which enables mapping database scalar functions to method stubs so that they can be used in LINQ queries and translated to SQL.

以下簡短描述說明如何使用此功能:Here is a brief description of how the feature can be used:

DbContext 上宣告靜態方法,並將它加上 DbFunctionAttribute 註釋:Declare a static method on your DbContext and annotate it with DbFunctionAttribute:

public class BloggingContext : DbContext
    public static int PostReadCount(int blogId)
        throw new Exception();

這類方法會自動予以註冊。Methods like this are automatically registered. 註冊後,LINQ 查詢中的方法呼叫可轉譯成 SQL 中的函式呼叫:Once registered, calls to the method in a LINQ query can be translated to function calls in SQL:

var query =
    from p in context.Posts
    where BloggingContext.PostReadCount(p.Id) > 5
    select p;

請注意幾件事:A few things to note:

  • 依照慣例,產生 SQL 時,方法的名稱會用作函式的名稱 (在此情況下,為使用者定義函式),但您可以在方法註冊期間覆寫名稱和結構描述By convention the name of the method is used as the name of a function (in this case a user defined function) when generating the SQL, but you can override the name and schema during method registration
  • 目前只支援純量函式Currently only scalar functions are supported
  • 您必須在資料庫中建立對應的函式,例如 EF Core 移轉不負責建立它You must create the mapped function in the database, e.g. EF Core migrations will not take care of creating it

Code First 的獨立類型組態Self-contained type configuration for code first

在 EF6 中,可能已封裝衍生自 EntityTypeConfiguration 之特定實體類型的 Code First 組態。In EF6 it was possible to encapsulate the code first configuration of a specific entity type by deriving from EntityTypeConfiguration. 在 EF Core 2.0 中,我們將重新使用這種模式:In EF Core 2.0 we are bringing this pattern back:

class CustomerConfiguration : IEntityTypeConfiguration<Customer>
  public void Configure(EntityTypeBuilder<Customer> builder)
     builder.HasKey(c => c.AlternateKey);
     builder.Property(c => c.Name).HasMaxLength(200);

// OnModelCreating
builder.ApplyConfiguration(new CustomerConfiguration());

高效能High Performance

DbContext 共用DbContext pooling

在 ASP.NET Core 應用程式中使用 EF Core 的基本模式,通常包含將自訂 DbContext 類型註冊到相依性插入系統,以及稍後透過控制器中的建構函式參數來取得該類型的執行個體。The basic pattern for using EF Core in an ASP.NET Core application usually involves registering a custom DbContext type into the dependency injection system and later obtaining instances of that type through constructor parameters in controllers. 這表示會為每個要求建立新的 DbContext 執行個體。This means a new instance of the DbContext is created for each requests.

在 2.0 版中,我們引進在相依性插入中註冊自訂 DbContext 類型的新方式,而相依性插入會以透明方式引進可重複使用 DbContext 執行個體的集區中。In version 2.0 we are introducing a new way to register custom DbContext types in dependency injection which transparently introduces a pool of reusable DbContext instances. 若要使用 DbContext 共用,請在服務註冊期間使用 AddDbContextPool,而非 AddDbContextTo use DbContext pooling, use the AddDbContextPool instead of AddDbContext during service registration:

    options => options.UseSqlServer(connectionString));

如果使用這種方法,則在控制器要求 DbContext 執行個體時,我們會先檢查集區中是否有執行個體可用。If this method is used, at the time a DbContext instance is requested by a controller we will first check if there is an instance available in the pool. 要求處理完成之後,會重設執行個體上的任何狀態,並將執行個體本身傳回到集區。Once the request processing finalizes, any state on the instance is reset and the instance is itself returned to the pool.

這在概念上類似連線共用如何在 ADO.NET 提供者中操作,而且優點是節省 DbContext 執行個體的一些初始化成本。This is conceptually similar to how connection pooling operates in ADO.NET providers and has the advantage of saving some of the cost of initialization of DbContext instance.


新的方法引進 DbContext 的 OnConfiguring() 方法中可進行作業的一些限制。The new method introduces a few limitations on what can be done in the OnConfiguring() method of the DbContext.


如果您在不應該於要求間共用的衍生 DbContext 類別中維護自己的狀態 (例如私用欄位),則請避免使用 DbContext 共用。Avoid using DbContext Pooling if you maintain your own state (e.g. private fields) in your derived DbContext class that should not be shared across requests. EF Core 只會重設在將 DbContext 執行個體新增至集區之前所知道的狀態。EF Core will only reset the state that is aware of before adding a DbContext instance to the pool.

明確地編譯查詢Explicitly compiled queries

這是設計成提供高延展案例優點的第二個選擇加入的效能功能。This is the second opt-in performance features designed to offer benefits in high-scale scenarios.

舊版 EF 以及 LINQ to SQL 中已提供手動或明確已編譯的查詢 API,允許應用程式快取查詢的轉譯,這樣只要將它們計算一次,就能執行多次。Manual or explicitly compiled query APIs have been available in previous versions of EF and also in LINQ to SQL to allow applications to cache the translation of queries so that they can be computed only once and executed many times.

一般而言,雖然 EF Core 可以根據查詢運算式的雜湊表示法來自動編譯並快取查詢 ,但是可以使用這項機制透過不計算雜湊和快取查閱來提升少許效能,以允許應用程式透過委派叫用來使用已編譯的查詢。Although in general EF Core can automatically compile and cache queries based on a hashed representation of the query expressions, this mechanism can be used to obtain a small performance gain by bypassing the computation of the hash and the cache lookup, allowing the application to use an already compiled query through the invocation of a delegate.

// Create an explicitly compiled query
private static Func<CustomerContext, int, Customer> _customerById =
    EF.CompileQuery((CustomerContext db, int id) =>
            .Include(c => c.Address)
            .Single(c => c.Id == id));

// Use the compiled query by invoking it
using (var db = new CustomerContext())
   var customer = _customerById(db, 147);

變更追蹤Change Tracking

附加可以追蹤全新和現有實體的圖形Attach can track a graph of new and existing entities

EF Core 支援透過不同的機制來自動產生索引鍵值。EF Core supports automatic generation of key values through a variety of mechanisms. 使用此功能時,如果重要屬性是 CLR 預設值,則會產生值:通常是零或 Null。When using this feature, a value is generated if the key property is the CLR default--usually zero or null. 這表示可以將實體的圖表傳遞至 DbContext.AttachDbSet.Attach,而且 EF Core 會將已設定索引鍵的實體標示為 Unchanged,同時將未設定索引鍵的實體標示為 AddedThis means that a graph of entities can be passed to DbContext.Attach or DbSet.Attach and EF Core will mark those entities that have a key already set as Unchanged while those entities that do not have a key set will be marked as Added. 使用產生的索引鍵時,這可以輕鬆地附加混合全新和現有實體的圖形。This makes it easy to attach a graph of mixed new and existing entities when using generated keys. DbContext.UpdateDbSet.Update 的運作方式相同,差別在於將設定索引鍵的實體標示為 Modified,而不是 UnchangedDbContext.Update and DbSet.Update work in the same way, except that entities with a key set are marked as Modified instead of Unchanged.


改善的 LINQ 轉譯Improved LINQ Translation

讓更多查詢順利執行,並在資料庫中評估更多邏輯 (而不是在記憶體中) 以及從資料庫擷取較少的資料。Enables more queries to successfully execute, with more logic being evaluated in the database (rather than in-memory) and less data unnecessarily being retrieved from the database.

GroupJoin 改善GroupJoin improvements

這項工作可改善針對群組聯結所產生的 SQL。This work improves the SQL that is generated for group joins. 群組聯結最常是選擇性導覽屬性的子查詢結果。Group joins are most often a result of sub-queries on optional navigation properties.

FromSql 和 ExecuteSqlCommand 中的字串插值String interpolation in FromSql and ExecuteSqlCommand

C# 6 已引進「字串插值」,此功能允許 C# 運算式直接內嵌在字串常值中,並提供不錯的方式在執行階段建置字串。C# 6 introduced String Interpolation, a feature that allows C# expressions to be directly embedded in string literals, providing a nice way of building strings at runtime. 在 EF Core 2.0 中,我們已在接受原始 SQL 字串的兩個主要 API 中新增內插字串的特殊支援:FromSql and ExecuteSqlCommand.In EF Core 2.0 we added special support for interpolated strings to our two primary APIs that accept raw SQL strings: FromSql and ExecuteSqlCommand. 這個新的支援允許以「安全」方式使用 C# 字串插值。This new support allows C# string interpolation to be used in a 'safe' manner. 亦即,可防止在執行階段動態建構 SQL 時可能發生的常見 SQL 插入錯誤。I.e. in a way that protects against common SQL injection mistakes that can occur when dynamically constructing SQL at runtime.

請看以下範例:Here is an example:

var city = "London";
var contactTitle = "Sales Representative";

using (var context = CreateContext())
            SELECT *
            FROM ""Customers""
            WHERE ""City"" = {city} AND
                ""ContactTitle"" = {contactTitle}")

在此範例中,有兩個以 SQL 格式字串內嵌的變數。In this example, there are two variables embedded in the SQL format string. EF Core 會產生下列 SQL:EF Core will produce the following SQL:

@p0='London' (Size = 4000)
@p1='Sales Representative' (Size = 4000)

FROM ""Customers""
WHERE ""City"" = @p0
    AND ""ContactTitle"" = @p1


我們已新增 EF.Functions 屬性,而 EF Core 或提供者用來定義可對應至資料庫函式或運算子的方法,以在 LINQ 查詢 中予以叫用。We have added the EF.Functions property which can be used by EF Core or providers to define methods that map to database functions or operators so that those can be invoked in LINQ queries. 這種方法的第一個範例是 Like():The first example of such a method is Like():

var aCustomers =
    from c in context.Customers
    where EF.Functions.Like(c.Name, "a%");
    select c;

請注意,Like() 隨附記憶體中實作,這在處理記憶體中資料庫或用戶端需要進行述詞評估時都很方便使用。Note that Like() comes with an in-memory implementation, which can be handy when working against an in-memory database or when evaluation of the predicate needs to occur on the client side.

資料庫管理Database management

DbContext Scaffolding 的複數攔截Pluralization hook for DbContext Scaffolding

EF Core 2.0 引進新的 IPluralizer 服務,以用來將實體類型名稱單數化,並將 DbSet 名稱複數化。EF Core 2.0 introduces a new IPluralizer service that is used to singularize entity type names and pluralize DbSet names. 預設實作是不操作,因此這只是人員可以輕鬆插入其專屬 pluralizer 的攔截。The default implementation is a no-op, so this is just a hook where folks can easily plug in their own pluralizer.

以下是開發人員在其專屬 pluralizer 中攔截的方式:Here is what it looks like for a developer to hook in their own pluralizer:

public class MyDesignTimeServices : IDesignTimeServices
    public void ConfigureDesignTimeServices(IServiceCollection services)
        services.AddSingleton<IPluralizer, MyPluralizer>();

public class MyPluralizer : IPluralizer
    public string Pluralize(string name)
        return Inflector.Inflector.Pluralize(name) ?? name;

    public string Singularize(string name)
        return Inflector.Inflector.Singularize(name) ?? name;


將 ADO.NET SQLite 提供者移至 SQLitePCL.rawMove ADO.NET SQLite provider to SQLitePCL.raw

這可在 Microsoft.Data.Sqlite 中提供更穩固的方案,以將原生 SQLite 二進位檔散發到不同的平台。This gives us a more robust solution in Microsoft.Data.Sqlite for distributing native SQLite binaries on different platforms.

一個模型只會有一個提供者Only one provider per model

大幅增加提供者如何與模型互動,以及簡化慣例、註釋和 Fluent API 如何與不同的提供者搭配運作。Significantly augments how providers can interact with the model and simplifies how conventions, annotations and fluent APIs work with different providers.

EF Core 2.0 現在會為使用的每個不同提供者建置不同的 IModelEF Core 2.0 will now build a different IModel for each different provider being used. 應用程式通常可以看到這項作業。This is usually transparent to the application. 這已加速簡化較低階中繼資料 API;因此,任何對一般關聯式中繼資料概念的存取一律是透過 .Relational 呼叫來進行,而非 .SqlServer.Sqlite 等等。This has facilitated a simplification of lower-level metadata APIs such that any access to common relational metadata concepts is always made through a call to .Relational instead of .SqlServer, .Sqlite, etc.

合併的記錄和診斷Consolidated Logging and Diagnostics

記錄 (根據 ILogger) 和診斷 (根據 DiagnosticSource) 機制現在共用更多程式碼。Logging (based on ILogger) and Diagnostics (based on DiagnosticSource) mechanisms now share more code.

在 2.0 中,已經變更傳送至 ILogger 之訊息的事件識別碼。The event IDs for messages sent to an ILogger have changed in 2.0. 在 EF Core 程式碼中,事件識別碼現在是唯一的。The event IDs are now unique across EF Core code. 例如,這些訊息現在也遵循 MVC 所使用結構化記錄的標準模式。These messages now also follow the standard pattern for structured logging used by, for example, MVC.

記錄器類別也已經變更。Logger categories have also changed. 現在已有一組類別可透過 DbLoggerCategory 進行存取。There is now a well-known set of categories accessed through DbLoggerCategory.

DiagnosticSource 事件現在會使用相同的事件識別碼名稱作為對應的 ILogger 訊息。DiagnosticSource events now use the same event ID names as the corresponding ILogger messages.