@Rays
2017-08-24T14:38:46.000000Z
字数 2730
阅读 1894
语言开发
Microsoft
摘要: 虽然EF Core 2.0存在大量槽点,但是它也给出了不少亮点。在本文中,我们将介绍这次发布版的部分亮点,其中包括对原始SQL使用字符串插值的安全方法。
作者: Jonathan Allen
正文:
虽然EF Core 2.0存在大量槽点,但是它也给出了不少亮点。在本文中,我们将介绍这次发布版的部分亮点。
ORM常被吐槽是总是对所请求数据做低效处理。默认情况下,大多数ORM执行采用了“SELECT *”的查询语言,这样的语句会请求数据表的全部列,即便应用只是需要其中的小部分子集。
为解决该问题,EF采用的传统方法是在查询中加入SELECT语句,将生成的SQL语句限制于查询所需要的列。但是这种方法在实现上或是需要使用第三方的映射库(例如AutoMapper),或是需要显式地列出每个所需的列到投影对象的映射关系。鉴于后一种方法的实现是相当的繁琐,并易于产生错误,在开发周期紧张的情况下开发人员通常跳过该步骤。这会导致数据库不能使用覆盖索引(Covering Index),进而导致一些查询的性能非常差。
另一个问题是,被投影的对象无法参与CRUD操作。这些对象或是需要映射回实体,或是需要在原始SQL调用中使用。
使用“数据表切分”(Table Splitting)是在EF Core中创建更具针对性查询的更好方法。我们可以将多个类映射到同一个数据库表上。要在EF Core中使用该方法,必须在所有的共享数据库表的实体类型之间配置一个“区别性关系”(在此关系上,外键属性构成了主键)。
我们已经知道,在EF中是无法可靠地创建全局过滤器。现在这一特性缺口已被EF Core填补。
全局过滤器允许开发人员对访问特定数据库表的所有查询额外添加一模一样的过滤器。它主要用于软删除(soft-delete)场景,即用户并不想返回那些被标记为已删除但是尚未从数据库中做物理移除的数据行。全局过滤器并非一个新概念,NHibernate、Tortuga Chain及其它一些ORM都使用了这一概念。
在EFCore中,全局过滤器是使用OnModelCreating事件中的如下代码实现的:
modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
该语法的确引发了一些担忧,因为它需要对每个和所有支持IsDeleted标识的数据库表做重复性的操作,当存在大量的数据库表时易于出错。但是相比于让开发人员努力去记住在每次查询中做检查,全局过滤器依然作用明显。
全局过滤器可以访问定义在DbContext对象中的域。这意味着,开发人员可以将其用于一些更高级的场景中,例如多租户环境。但是在上下文池(Context Pool)中使用它时,不能掉以轻心(参见下文)。
在文档中给出了全局过滤器的两个局限性:
虽然创建DbContext对象的代价要显著地低于创建数据库连接,但是前者依然会导致性能下降。一个理想的解决方案是让DbContext是线程安全的,但是EF的设计并不支持。
为了对ASP.NET Core应用解决该问题,现在EF Core提供了DbContext Pooling特性。该特性是基于ASP.NET Core的依赖注入(Dependency Injection)框架实现的。
文档中给出了一个警告,即对于多租户使用全局过滤器的应用场景,上下文池并不兼容。
如果开发人员在派生的DbContext类中维护自己的状态(例如私有域),并且该DbContext类不应在请求间共享,那么应避免使用DbContext Pooling特性。EF Core仅是在添加一个DbContext实例到池中之前,重置了它所了解的状态。
通过“reset”方法,或是DI框架所调用的事件,就可以轻易地解决这一问题。鉴于此,我们希望该问题会在未来的版本中得到修正。
在数据存放之处执行代码,这是数据库服务器的一个关键特性。如果我们能合理地使用该特性,那么相比于在处理前将数据传输给应用的方法,查询性能可以得到显著的提升。
EF Core 2通过暴露标量函数,添加了对此特性的支持。标量函数是一种定义在EF模型中的静态函数,它的函数体为空,并标记了DbFunction属性。LINQ提供者会检测这些标量函数的使用,并在生成的SQL语句中使用相应的服务器端函数。
当前EF Core仅支持上述方式的标量函数。除非直接使用原始SQL语句,目前EF Core尚不提供对更为强大的表值函数(Table Valued Functions)的支持。
如何让字符串插值与EF Core一起工作,这是一个很有意思的特性。EF Core并非是简单地转换为一个String.Format调用,而是发出一个参数化SQL字符串。这样做对于避免SQL注入攻击非常关键。
var city = "Redmond";
context.Customers.FromSql($"SELECT * FROM Customers WHERE City = {city}");
SELECT * FROM Customers WHERE City = @p0
我们也许可以期待,该技术将会被各种micro ORM所采纳。
现在,我们可以将查询以委托的形式缓存(即函数指针),通常定义为一个匿名方法。这不仅提高了查询性能,而且是一种可在多处使用同一查询的便利方法。委托将接受一个DbContext对象为参数,因此也可用于多语句事务(Multi-Statement Transaction)中。
请注意,在一些情况下,查询是会自动做缓存的。因此,我们不能对性能的增加期待过高。
EF Core通常会根据查询表达式的哈希表示而自动地编译并缓存查询。尽管如此,该机制通过旁路哈希和缓存查找的计算,使得应用可以通过调用委托去使用已经被编译过的查询,这依然是可以取得一定程度上的性能增加的。
这些新特性并非凭空给出的。在EF Core系列文章的第三部分中,我们将介绍EF Core 2.0的一些突破性改进。