前言
這是.Net Core 2.0生態生態介紹的最后一篇,EF一直是我喜歡的一個ORM框架,隨着版本升級EF也發展到EF6.x,Entity Framework Core是一個支持跨平台的全新版本,可以用三個詞來概況EF Core的特點:輕量級、可擴展、跨平台。跨平台的特性是EF6.x無法替代的優勢,也許會成為你在項目中技術選型的原因之一。
對於.NET Core 2.0的發布介紹,圍繞2.0的架構體系,本系列相關文章:
- .Net Core 2.0 生態(1).NET Standard 2.0 特性介紹和使用指南(已發布)
- .Net Core 2.0 生態(2).NET Core 2.0 特性介紹和使用指南(已發布)
- .Net Core 2.0 生態(3)ASP.NET Core 2.0 特性介紹和使用指南(已發布)
- .Net Core 2.0 生態(4)Entity Framework Core 2.0 特性介紹和使用指南(已發布)
獲取和使用
在命令行工具安裝NuGet包,比如:安裝SQL Server EF Core提供程序,並指定版本為2.0.0
。
$ dotnet add package Microsoft.EntityFrameworkCore.SqlServer -V 2.0.0
在VS2017中使用包管理器控制台安裝
PM> Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 2.0.0
在ASP.NET Core 2.0默認項目包含支持EF Core 2.0的SQL Server, SQLite, 和 in-memory數據庫提供程序,創建項目無需額外添加。
查看在不同平台上使用EF Core指南,查看更多安裝和升級細節,進入幫助文檔。
新特性
以下是最顯著的新特性:
-
使用.NET Standard 2.0目標框架:這使得EF Core 2.0可支持多種.NET平台實現和應用程序類型,查看平台支持列表。
-
LINQ解析改進:EF Core 2.0中的查詢更加高效,適應多種場景。舉個例子,增加了翻譯成SQL語句模式的數量限制,避免了在以前版本中因為客戶端計算導致多重查詢的問題。(優化了客戶端計算性能)
-
Like查詢支持:LINQ查詢可以使用
EF.Functions.Like()
,最終解析為SQL中的like
語句,在必要的時候會進行內存計算,舉個例子,下面的查詢:var customers = from c in context.Customers where EF.Functions.Like(c.Name, "a%"); select c;
解析成SQL語句:
SELECT [c].[Id], [c].[Name] FROM [Customers] AS [c] WHERE [c].[Name] LIKE N'a%';
和SQL中like語句一樣使用通配符。
-
實體包含關系和表拆分:可以通過屬性關聯建立實體之間的包含關系,這個特性和EF6中的復合類型類似,只需要定義一個導航屬性。實體包含關系定義與表拆分結合使用,可以將兩個實體自動映射為單張表,參看下面的示例:
public class Customer { public int Id { get; set; } public string Name {get; set;} public PhysicalAddress Address { get; set; } } public class PhysicalAddress { public string StreetAddress { get; set; } public Location Location { get; set; } } ... modelBuilder.Entity<Customer>() .OwnsOne(c => c.Address);
-
全局查詢過濾器:在DbContext中為實體定義查詢過濾器,下面代碼在
OnModelCreating
方法中定義:modelBuilder.Entity<Post>() .HasQueryFilter(p => !p.IsDeleted);
下面的查詢只會返回未被標記為刪除的結果:
var blog = context.Blogs .Include(b => b.Posts) .FirstOrDefault(b => b.Id == id);
這個特性在特殊的業務場景下將有大用處,比如多租戶中數據過濾實現。
-
DbContext Pooling(池):這項特性能夠顯著提升Asp.net Core應用程序的性能,通過在服務注冊
DbContext
類型時啟用,使用預先創建的實例池,避免為每個請求創建新實例:services.AddDbContextPool<BloggingContext>( options => options.UseSqlServer(connectionString));
這是一個最佳實踐,推薦使用!
-
SQL方法支持字符串插值:下面的SQL語句使用了C#中字符串插值語法,簡化參數化查詢:
var city = "Redmond"; using (var context = CreateContext()) { context.Customers.FromSql($@" SELECT * FROM Customers WHERE City = {city}"); }
以上代碼轉換為SQL語句會創建一個名為
@p0
的參數,值為Redmond
,生成如下SQL語句:SELECT * FROM Customers WHERE City = @p0
-
更多特性:如:顯式編譯查詢、自包含實體配置、數據庫標准函數映射。還修復了許多Bug。詳細內容參考:新功能
項目升級和核心API變化
- 將項目.NET平台設置為支持.NET Standard 2.0的平台
- 使用支持2.0的數據庫提供程序
- 更新EF Core引用包(包括運行時和工具)到2.0
- 必要的時候對代碼進行修改,具體參看核心變化
在2.0版本中,部分API和操作有較大改進,有部分改進需要修改現有程序代碼,對於大多數應用程序來說,影響不大,大多數情況下,只需要重新編譯和最少的修改來替換過時的API。
獲取應用程序服務新方式
注:EF Core在設計時的操作比如生成數據遷移代碼,更新數據庫,需要訪問應用程序服務。設計時工具和應用程序存在調用關系。
推薦將ASP.NET Core Web應用程序更新到2.0,在ASP.NET Core 2.0在啟動類之外初始化配置。在之前的版本EF Core嘗試執行Startup.ConfigureServices
,直接訪問應用程序的服務提供者,使用EF Core的應用程序通常從配置文件中訪問連接字符串,所以單靠Startup
已經不能滿足獲取連接字符串的需要。
更新ASP.NET Core 1.x到2.0,當使用了EF Core工具,會收到如下錯誤提示:
No parameterless constructor was found on 'ApplicationContext'. Either add a parameterless constructor to 'ApplicationContext' or add an implementation of 'IDesignTimeDbContextFactory
' in the same assembly as 'ApplicationContext'
在ASP.NET Core 2.0默認模板中新增設計時支持,靜態方法Program.BuildWebHost
允許EF Core在設計時訪問應用程序服務提供者,如果升級ASP.NET Core 1.x應用程序,同時升級Program
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
namespace AspNetCoreDotNetCore2._0App
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}
注:如果沒有ASP.NET Core 2.0應用程序沒有更改Program啟動方式,依然可以使用實現
IDesignTimeDbContextFactory<ApplicationContext>
接口方式提供EF Core 2.0設計時支持,不推薦這么做。
IDbContextFactory改名
為了支持不同的應用模式,在設計時提供對DbContext
更多自定義控制,在以前的版本提供接口IDbContextFactory<TContext>
,EF Core工具在設計時,會發現應用程序中該接口的實現並使用它來創建DbContext
對象。
這個接口因為具有通用性的名稱,容易誤導開發者使用來處理需要重新創建DbContext
的開發場景,當在設計時EF Core工具使用它時因為沒有考慮到設計時的特殊環境可以導致Update-Database
或dotnet ef database update
命令執行失敗。
基於以上的原因,為了更精准的表達該接口的作用,我們將其改名為IDesignTimeDbContextFactory<TContext>
,在2.0中IDbContextFactory<TContext>
仍然存在,但是已經標記為過時了。
DbContextFactoryOptions移除
因為ASP.NET 2.0的升級,我們發現在接口IDesignTimeDbContextFactory<TContext>
不在需要DbContextFactoryOptions
。
下面是你應該使用的替代方案。
- DbContextFactoryOptions.ApplicationBasePath 使用AppContext.BaseDirectory代替
- DbContextFactoryOptions.ContentRootPath 使用Directory.GetCurrentDirectory()代替
- DbContextFactoryOptions.EnvironmentName 使用Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")代替
EF Core 2.0需要2.0數據庫提供程序
對於EF Core 2.0,我們已經對數據庫提供程序的工作進行了許多簡化和改進。1.0.x和1.1.x提供程序已經不能在EF Core 2.0下工作。
由EF團隊開發的SQL Server和SQLite數據庫提供程序,2.0版本將在2.0版本中提供。其他數據庫提供程序也已經升級到2.0版本:
日志記錄和診斷事件更改
注意:這些更改不會影響大多數的應用程序代碼。
發送給ILogger的消息的事件ID在2.0中發生了變化。現在在EF Core中事件ID是全局唯一的。這些消息現在也遵循了結構化日志的標准模式,例如,MVC。
Logger類別也發生了變化,類別可通過DbLoggerCategory
訪問。
DiagnosticSource事件現在使用與對應ILogger消息相同的時間ID名稱,事件ID、有效負載類型和類別進行了統一。
ID從Microsoft.EntityFrameworkCore.Infraestructure
命名空間移到Microsoft.EntityFrameworkCore.Diagnostics
。
EF Core元數據關系API變化
EF Core 2.0為不同的提供程序創建一個不同的IModel
,這通常對應用程序是透明的,從而簡化了底層元數據API,使得任何對公共關系的元數據都可以通過調用來實現,對比如下代碼,在1.1.x中代碼:
var tableName = context.Model.FindEntityType(typeof(User)).SqlServer().TableName;
現在可以這么寫
var tableName = context.Model.FindEntityType(typeof(User)).Relational().TableName;
更具通用性。
在比如使用方法ForSqlServerToTable
,現在可以使用更加通用的代碼來實現
modelBuilder.Entity<User>().ToTable(
Database.IsSqlServer() ? "SqlServerName" : "OtherName");
請注意,此更改僅適用於為所有關系提供程序定義的API/元數據。當只針對單個提供者時,API和元數據仍然是相同的。舉個例子,聚集索引是SQL Server特有的,所以ForSqlServerIsClustered
和.SqlServer().IsClustered()
必須使用。
不要控制EF服務提供程序
EF Core使用內置IServiceProvider
在框架內部實現,應用程序應該允許EF Core創建和管理這個提供程序。強烈建議刪除所有UseInternalServiceProvider
的調用,AddEntityFramework
,AddEntityFrameworkSqlServer
不需要通過應用程序代碼調用,建議移除。AddDbContext
的使用方式和以前一樣。
內存數據庫必須命名
全局匿名內存數據庫已經被刪除,而所有內存數據庫都必須被命名。
optionsBuilder.UseInMemoryDatabase("MyDatabase");
名稱相同就算調用多次,仍然使用同一個數據庫,允許由多個上下文實例共享。
Read-only API 變化
IsReadOnlyBeforeSave
, IsReadOnlyAferSave
, 和 IsStoreGeneratedAlways
已經過時,由 BeforeSaveBehavior
和 AfterSaveBehavior
代替。這些行為應用到任何屬性(不僅是存儲生成的屬性)並檢測屬性值如何被使用,比如插入數據庫行(BeforeSaveBehavior)或更新現有數據庫行(AfterSaveBehavior)。
屬性通過ValueGenerated.OnAddOrUpdate
進行標記,例如:計算列。默認情況下,將忽略當前設置在該屬性上的任何值。這意味着無論是否在跟蹤實體上設置或修改了任何值,都將始終獲得一個存儲生成的值。這可以通過設置不同的Before\AfterSaveBehavior
來改變。
新的ClientSetNull刪除行為
在以前的版本中DeleteBehavior.Restrict
通過上下文對實體有一個跟蹤行為,這些實體與SetNull語義更加封閉。在EF Core 2.0中一個新的行為ClientSetNull
作為可選關系的默認值。此行為為跟蹤實體設置了SetNull語義,並限制使用EF Core創建的數據庫的行為。根據我們的經驗這對跟蹤實體和數據庫是非常有用的。
設計時提供程序更改
Microsoft.EntityFrameworkCore.Relational.Design
引用包移除,功能被整合進Microsoft.EntityFrameworkCore.Relational
和 Microsoft.EntityFrameworkCore.Design
引用包。
不同數據設計時引用包,比如Microsoft.EntityFrameworkCore.Sqlite.Design
, Microsoft.EntityFrameworkCore.SqlServer.Design
,整合進其主提供程序中。
在EF Core 2.0中啟用Scaffold-DbContext
或 dotnet ef dbcontext scaffold
現在只需要引用單一的包
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer"
Version="2.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools"
Version="2.0.0"
PrivateAssets="All" />
<DotNetCliToolReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet"
Version="2.0.0" />
下一步計划
已經在進行下一個版本的開發,查看開發計划,另外也在完成EF 6.2。
遺憾的地方
- 不支持GroupBy、延遲加載——這兩個特性都在2.1計划中