.Net Core 2.0生态(4):Entity Framework Core 2.0 特性介绍和使用指南
前言
这是.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计划中