.NET 標準 2.0
EF Core 現在的目標是 .NET Standard 2.0,這意味著它可以與 .NET Core 2.0、.NET Framework 4.6.1 以及其他實作 .NET Standard 2.0 的函式庫相容。 更多支援內容請參閱 支援的 .NET 實作 。
建 模
檔案分割
現在可以將兩種或多種實體類型映射到同一個表格,主鍵欄位會共享,且每列對應兩個或多個實體。
要使用表格拆分,必須在所有共享該表格的實體類型間設定一個識別關係(其中外鍵屬性為主鍵):
modelBuilder.Entity<Product>()
.HasOne(e => e.Details).WithOne(e => e.Product)
.HasForeignKey<ProductDetails>(e => e.Id);
modelBuilder.Entity<Product>().ToTable("Products");
modelBuilder.Entity<ProductDetails>().ToTable("Products");
請閱讀 分桌 部分,了解更多關於此功能的資訊。
擁有的型號
一個被擁有的實體類型可以使用與另一個被擁有的實體類型相同的 .NET 類型,但由於無法僅依靠 .NET 類型來識別,必須有從其他實體類型指向它的導覽。 包含定義導航的實體是擁有者。 查詢擁有者時,預設會包含擁有的類型。
依慣例,會為所屬型別建立一個隱藏主鍵,並使用資料表拆分技術將其映射到與所有者相同的資料表。 這使得自定義型別的使用方式類似於 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; }
}
如需此功能的詳細資訊,請閱讀 擁有實體類型部分。
模型層級查詢過濾器
EF Core 2.0 包含一項我們稱為模型層級查詢過濾器的新功能。 此功能允許在元資料模型中直接於實體類型上定義 LINQ 查詢謂詞(布林表達式,通常傳遞給 LINQ 的 Where 查詢運算子),通常是在 OnModelCreating 方法中進行。 此類篩選器會自動套用於任何涉及這些實體類型的 LINQ 查詢,包括透過如 Include 使用或直接使用導覽屬性而間接參照的實體類型。 此功能的一些常見應用包括:
- 軟刪除 - Entity Types 定義了 IsDeleted 屬性。
- 多租戶 - 實體類型定義 TenantId 屬性。
以下是一個簡單範例,示範上述兩種情境的特性:
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)
{
modelBuilder.Entity<Post>().HasQueryFilter(
p => !p.IsDeleted
&& p.TenantId == this.TenantId);
}
}
我們定義了一個模型級別的過濾器,用於實現多租戶和實體類型Post實例的軟刪除。 請注意使用 DbContext 實例層級屬性: TenantId。 模型層級過濾器會使用正確的上下文實例(即執行查詢的上下文實例)的值。
可使用 IgnoreQueryFilters() 運算子,對個別 LINQ 查詢關閉篩選器。
局限性
- 不允許使用導航參考。 此功能可根據回饋加入。
- 過濾器只能在階層結構的根實體類型上定義。
資料庫純量函數映射
EF Core 2.0 包含 Paul Middleton 的重要貢獻,該貢獻使資料庫純量函式映射到方法存根,使其可用於 LINQ 查詢並轉譯成 SQL。
以下是該功能的簡要使用說明:
在 DbContext 宣告一個靜態方法,並使用 DbFunctionAttribute 標註:
public class BloggingContext : DbContext
{
[DbFunction]
public static int PostReadCount(int blogId)
{
throw new NotImplementedException();
}
}
這類方法會自動註冊。 一旦註冊,LINQ 查詢中對方法的呼叫可以轉換成 SQL 中的函式呼叫:
var query =
from p in context.Posts
where BloggingContext.PostReadCount(p.Id) > 5
select p;
有幾點需要注意:
- 依慣例,方法名稱在產生 SQL 時會用作函式名稱(此處為使用者定義函式),但在方法註冊時可以覆寫名稱與結構。
- 目前僅支援純量函數。
- 你必須在資料庫中建立映射函式。 EF Core 的遷移不會負責建立它。
程式碼優先的自包含型態配置
在 EF6 中,可以透過從 EntityTypeConfiguration 衍生出特定實體類型的程式碼優先配置來封裝。 在 EF Core 2.0 中,我們將此模式重新啟用:
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());
高效能
DbContext 共用
在 ASP.NET Core 應用程式中使用 EF Core 的基本模式通常是將自訂的 DbContext 類型註冊到相依注入系統,之後透過控制器中的建構參數取得該類型的實例。 這表示每個請求都會建立一個新的 DbContext 實例。
在 2.0 版本中,我們將引入一種新的方式,在依賴注入中註冊自訂 DbContext 類型,透明地引入一個可重複使用的 DbContext 實例池。 要使用 DbContext pooling,請在服務註冊期間使用 AddDbContextPool 取代 AddDbContext:
services.AddDbContextPool<BloggingContext>(
options => options.UseSqlServer(connectionString));
若使用此方法,當控制器請求 DbContext 實例時,我們會先檢查池中有沒有實例可用。 請求處理完成後,實例上的任何狀態會被重置,實例本身會被回傳回池。
這在概念上類似於 ADO.NET 提供者中的連線池運作方式,且其優點是節省部分 DbContext 實例初始化的成本。
局限性
新方法在 DbContext 的 OnConfiguring() 方法中引入了一些限制。
警告
如果你在衍生的 DbContext 類別中維護你自己的狀態(例如私有欄位),而這些狀態不應在請求間共享,請避免使用 DbContext Pooling。 EF Core 只會在將 DbContext 實例加入池之前,重設其已知的狀態。
顯式編譯查詢
這是第二個選擇加入的效能功能,旨在於高規模情境下提供優勢。
手動或明確編譯的查詢 API 在先前版本的 EF 以及 LINQ 轉 SQL 中也已提供,讓應用程式能快取查詢的轉譯,使查詢只能計算一次並執行多次。
雖然一般來說 EF Core 可以根據查詢表達式的雜湊表示自動編譯與快取查詢,但這種機制可透過繞過雜湊值與快取查找的計算,藉由應用程式透過呼叫代理來使用已編譯好的查詢,從而獲得小幅效能提升。
// Create an explicitly compiled query
private static Func<CustomerContext, int, Customer> _customerById =
EF.CompileQuery((CustomerContext db, int id) =>
db.Customers
.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);
}
變更追蹤
Attach 可以追蹤新舊實體的變動圖表
EF Core 支援透過多種機制自動產生關鍵值。 使用此功能時,若鍵屬性為 CLR 預設值,通常為零或空值,則會產生一個值。 這表示可以將實體圖傳送到 DbContext.Attach 或 DbSet.Attach ,EF Core 會將已設定金鑰的實體標記為 Unchanged ,而尚未設定鍵組的實體則標記為 Added。 這使得在使用生成的鍵時,能輕鬆附加包含新舊實體的混合圖表。
DbContext.Update 及 DbSet.Update 的運作方式相同,不同之處在於具有鍵集的實體會被標記為 Modified,而不是 Unchanged。
查詢
改良版 LINQ 翻譯
透過在資料庫中評估更多邏輯(而非在記憶體中),成功執行更多查詢,同時減少不必要的資料從資料庫中取出。
GroupJoin 改進
這項工作改進了用於群組連接產生的 SQL。 群組連接通常是因為對可選導覽屬性的子查詢而產生。
FromSql 與 ExecuteSqlCommand 中的字串插值
C# 6 引入了字串插值功能,這項功能允許 C# 表達式直接嵌入字串字面值中,提供一種在執行時建立字串的好方法。 在 EF Core 2.0 中,我們為兩個主要接受原始 SQL 字串的 API 加入了插值字串的特殊支援: FromSql 和 ExecuteSqlCommand。 這項新支援讓 C# 字串插值能以「安全」的方式使用。 也就是說,要以一種能防止在執行時動態建構 SQL 時常見的 SQL 注入錯誤的方式。
以下是範例:
var city = "London";
var contactTitle = "Sales Representative";
using (var context = CreateContext())
{
context.Set<Customer>()
.FromSql($@"
SELECT *
FROM ""Customers""
WHERE ""City"" = {city} AND
""ContactTitle"" = {contactTitle}")
.ToArray();
}
在這個範例中,SQL 格式字串中嵌入了兩個變數。 EF Core 將產生以下 SQL:
@p0='London' (Size = 4000)
@p1='Sales Representative' (Size = 4000)
SELECT *
FROM ""Customers""
WHERE ""City"" = @p0
AND ""ContactTitle"" = @p1
EF.Functions.Like()
我們已經加入了EF。函數屬性,EF Core 或提供者可用來定義映射到資料庫函式或運算子的方法,使這些運算子能在 LINQ 查詢中被調用。 此類方法的第一個範例是 Like():
var aCustomers =
from c in context.Customers
where EF.Functions.Like(c.Name, "a%")
select c;
請注意,Like() 內建記憶體實作,當處理記憶體資料庫或需要在客戶端評估謂詞時,這非常實用。
資料庫管理
DbContext 框架的複數化掛鉤
EF Core 2.0 引入了一項新的 IPluralizer 服務,用於單數化實體類型名稱並複數化 DbSet 名稱。 預設實作是不執行任何操作,所以這是個掛鉤,讓大家可以輕鬆插入自己的複數化工具。
以下是開發者掛鉤自己的複數化工具時的樣貌:
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.raw
這讓我們在 Microsoft.Data.Sqlite 中擁有更穩健的解決方案,能在不同平台上分發原生 SQLite 二進位檔。
每個模型只能有一位提供者
大幅提升提供者與模型互動的方式,並簡化慣例、註解與流暢 API 在不同提供者間的運作方式。
EF Core 2.0 現在會為每個不同的提供者建置不同的 IModel 。 這通常對應用程式來說是透明的。 這促進了低階元資料 API 的簡化,使得任何對常見關聯性元資料概念的存取,一律透過呼叫 .Relational,而不再使用 .SqlServer、.Sqlite 等方法。
整合日誌與診斷
日誌(基於 ILogger)與診斷機制(基於 DiagnosticSource)現在共享更多程式碼。
發送給 ILogger 的訊息事件 ID 在 2.0 版本中有所更改。 事件 ID 現在在 EF Core 程式碼中是唯一的。 這些訊息現在也遵循了例如MVC等標準的結構化日誌模式。
日誌記錄器類別也有所變動。 現在有一套知名的分類可透過 DbLoggerCategory 存取。
DiagnosticSource 事件現在使用與對應 ILogger 訊息相同的事件 ID 名稱。