前言
Entity Framework Core 2.0更新也已经有一段时间了,园子里也有不少的文章..
看了下2.0的新特性基本算是完成了我之前发布的路线图的内容 很不错
下面就介绍一下新特性.(本文的英文原文地址:这里)
现在可以将多个实体类型映射到将要共享主键列的同一个表,并且每一行将对应于两个或多个实体。
使用表拆分识别关系(其中外键属性形成主键)必须在共享表的所有实体类型之间进行配置:
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");
拥有的实体类型可以与另一个拥有相同的实体类型共享CLR类型,但是由于CLR类型不能被识别,所以必须从另一个实体类型导航到它。包含定义导航的实体是所有者。当查询所有者时,默认情况下将包含所有类型。
按照惯例,将为所属类型创建一个影子主键,并通过使用表分割将其映射到与所有者相同的表。使用所属类型与EF6中使用复杂类型类似,(PS:这里解释一下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; }
}
此功能允许在元数据模型(一般在OnModelCreating)中直接在实体类型上定义LINQ查询条件(通常传递给LINQ Where查询运算符的布尔表达式)。这些过滤器自动应用于涉及这些实体类型的任何LINQ查询,包括间接引用的实体类型,例如通过使用Include或直接导航属性引用。
嗯..软删除,多租户的数据库设计 可以大量的使用这方面的功能,会减少很多代码量
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 );
}
}
这是一个很有用的功能,我们知道,我们的数据库一般有很多自带的数据库函数,或者我们会定义一些标量的函数.
通过这个特性 我们可以很方便的在linq中调用这些函数,并通过linq翻译成SQL
代码如下:
public class BloggingContext : DbContext
{
[DbFunction]
public static int PostReadCount(int blogId)
{
//这里不需要实现
throw new Exception();
}
}
然后直接就可以在linq查询中使用了 如下:
var query =
from p in context.Posts
where BloggingContext.PostReadCount(p.Id) > 5
select p;
值得注意的是:
在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实例初始化成本。
这是一个可选的性能功能,主要是为了在大规模场景中提供优势。
显式编译的查询API已经在以前版本的EF和LINQ to SQL中可用,以允许应用程序缓存查询的翻译,以便它们只能被计算一次并执行多次。
虽然EF Core通常可以根据查询表达式的散列表示自动编译和缓存查询,但这种机制可以通过绕过哈希计算和高速缓存查找来获得小的性能增益,从而允许应用程序使用已经通过调用委托编译了查询。
代码如下:
// 创建一个显示编译的查询
private static Func<CustomerContext, int, Customer> _customerById =
EF.CompileQuery((CustomerContext db, int id) =>
db.Customers
.Include(c => c.Address)
.Single(c => c.Id == id));
// 引用并使用它
using (var db = new CustomerContext())
{
var customer = _customerById(db, 147);
}
使更多的查询成功执行,并将更多的逻辑生成SQL让它在数据库中执行(而不是内存中),并且从数据库中检索更少的不必要的数据。
此工作改进了为组连接生成的SQL。
C#6(C#6.0特性请移步:这里)中引入了字符串插值,这是一个允许C#表达式直接嵌入到字符串文字中的功能,提供了一种在运行时构建字符串的好方法。
在EF核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语句:
@p0='London' (Size = 4000)
@p1='Sales Representative' (Size = 4000)
SELECT *
FROM ""Customers""
WHERE ""City"" = @p0
AND ""ContactTitle"" = @p1
添加了EF.Functions属性(注意,这里应该是可以扩展的,添加更多的数据库方法),EF Core可以使用它们来定义映射到数据库函数或操作符的方法,以便可以在LINQ查询中调用它们。这样一个方法的第一个例子是Like():
var aCustomers =
from c in context.Customers
where EF.Functions.Like(c.Name, "a%");
select c;
值得注意的是,Like方法带有内存中的实现,当对内存中的数据进行查询时,或者在客户端需要发生相关的内存查询时,可以方便很多.