Entity Framework Core 2.1 Preview1 新增功能简介
两个星期前,微软发布了EF Core 2.1 Preview 1,同时还发布了.NET Core 2.1 Preview 1和ASP.NET Core 2.1 Preview 1;EF Core 2.1 Preview 1 除了许多小改进和超过100种产品错误修复之外,还包括几个常用的新功能,今天我为您详细介绍这些新功能的部分内容。
实体构造函数参数
EF.Core 2.1开始支持在实体的构造函数的实体中转入参数,目前支持的类型如下:
- 实体属性
- IOC容器中注册的服务
- 当前的DbContext
- 当前实体的元数据
实体属性
在某些情况下为了保证数据的安全性,将属性改为只读,在构造函数中传递属性的值,框架通过参数与属性匹配关系,将数据行中属性的值作为参数传递给构造函数。
例如下面的实体:
public class Order
{
public Order(int orderID, string customerID, DateTime? orderDate)
{
OrderID = orderID;
CustomerID = customerID;
OrderDate = orderDate;
}
public int OrderID { get; }
public string CustomerID { get; }
public DateTime? OrderDate { get; }
}
其中参数与属性的配置规则如下:
参数的类型与属性的类型一致;
属性名与参数名除首字母不区分大小写之外,其它字符一致,并且可以使用
_
、m_
做为前缀,使用OrderID
属性来举例,存在如下匹配规则:属性名 参数名 OrderID
OrderID
OrderID
orderID
_OrderID
orderID
_OrderID
OrderID
m_OrderID
OrderID
m_OrderID
OrderID
具体的匹配规则可以见Github上面的源代码:https://github.com/aspnet/EntityFrameworkCore/blob/8965f0b91cf89e36abca8636d58420cbd26c22fd/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs#L37-L45
不过我认识后面四种模式有待斟酌的,在.Net开发规范,应该没有人将公有的属性名使用 _、m_作为前缀。
IOC容器中注册的服务
在实体的构造函数的中,可以将注册的服务作为参数。
示例代码:
public class Order
{
private ILazyLoader _lazyLoader;
public Order(ILazyLoader lazyLoader)
{
this._lazyLoader = lazyLoader;
}
public int OrderID { get; set; }
public string CustomerID { get; set; }
private ICollection<OrderDetail> _orderDetails;
public ICollection<OrderDetail> OrderDetails
{
get => _lazyLoader.Load(this, ref _orderDetails);
set => _orderDetails = value;
}
}
}
其中ILazyLoader
是EF Core框架在容器中注册的一个服务,通过实体的构造函数中传入,实现导航属性的赖加载(关于ILazyLoader
的具体使用方式在本章的下一节中讲解)。
当前的DbContext
在实体的构造函数的参数中,将当前的DbContext
作为参数。
示例代码:
public class Order
{
private NorthwindContext _northwindContext;
public Order(NorthwindContext northwindContext)
{
this._northwindContext = northwindContext;
}
public int OrderID { get; set; }
public string CustomerID { get; set; }
private ICollection<OrderDetail> _orderDetails;
[NotMapped]
public ICollection<OrderDetail> OrderDetails
{
get
{
if (this._orderDetails == null)
this._orderDetails = this._northwindContext.Set<OrderDetail>()
.Where(item => item.OrderID == this.OrderID).ToList();
return this._orderDetails;
}
set => _orderDetails = value;
}
}
当前实体的元数据
在实体的构造函数的参数中,将当前实体的的IEntityType
作为参数。
示例代码:
public class Order
{
private IEntityType _entityType;
public Order(IEntityType entityType)
{
this._entityType = entityType;
}
public int OrderID { get; set; }
public string CustomerID { get; set; }
[NotMapped]
public IEntityType EntityType
{
get { return this._entityType; }
}
}
如果实体存在多个构造函数,框架会选择参数个数最多的那个;如果按参数个数优先选择后,依然存在多个构造函数,则会抛异常。在当前体验版本中,暂时无法直接支持自定义参数,不过在下一个发布版本中,会提供解决方案。
懒加载
懒加载是一个非常有争论的功能激烈争论的功能。虽然有些人认为它会导致性能下降或出现意想不到的Bug,但是不影响有些开发人员依旧喜欢它。EF Core 2.1 Preview 1增加了懒加载,提供了两种实现方式。
使用ILazyLoader接口实现懒加载
在实体的构造函数中传入ILazyLoader
,在导航属性中,使用接口的Load
方法,实现导航属性的数据加载。
示例代码:
public class Order
{
private ILazyLoader _lazyLoader;
public Order(ILazyLoader lazyLoader)
{
this._lazyLoader = lazyLoader;
}
public int OrderID { get; set; }
public string CustomerID { get; set; }
public DateTime? OrderDate { get; set; }
private ICollection<OrderDetail> _orderDetails;
public ICollection<OrderDetail> OrderDetails
{
get => this._lazyLoader.Load(this, ref _orderDetails);
set => _orderDetails = value;
}
}
通过代理类实现懒加载
这种方式,需要单独安装 Microsoft.EntityFrameworkCore.Proxies Nuget
包,它通过 Castle.Core 框架来生成代理类来实现对导航属性的延迟加载。
启用懒加载需要注意以下两点:
- 在配置中启用懒加载;
- 实体类不能是封闭(sealed)类,导航属性必须是虚(virtual)属性。
这种方式,在以前的博客我已经分享过,只不过当时还没有发布,原文地址:Entity Framework Core 懒加载。
值转换
EF Core 2.1 允许您将插入数据库的值自定义转换逻辑。例如:将属性的值进行加密与解密。
示例,将插入的值进行Base64编码,在查询的时候进行Base64解码。
定义的UserInfo
实体,用于保存用户信息,属性PhoneNumber
表示用户的手机号码;为了用户信息安全,需要将手机号码进行加密后再保存到数据库,只是为了达到演示的目的,我们采用Base64进行编码。
public class UserInfo
{
public int Id { get; set; }
public string PhoneNumber { get; set; }
}
Base64ValueConverter
表示进行值转换的具体逻辑,继承自泛型ValueConverter<string, string>
,具体的逻辑非常简单,不再叙述。
public class Base64ValueConverter : ValueConverter<string, string>
{
public Base64ValueConverter() : base((v) => ToBase64(v), (v) => FromBase64(v))
{
}
private static string ToBase64(string input)
{
if (string.IsNullOrEmpty(input))
return input;
var bytes = Encoding.UTF8.GetBytes(input);
return Convert.ToBase64String(bytes);
}
private static string FromBase64(string input)
{
if (string.IsNullOrEmpty(input))
return input;
var bytes = Convert.FromBase64String(input);
return Encoding.UTF8.GetString(bytes);
}
}
SampleDbContext
表示数据上下文,在OnModelCreating
方法中,定义UserInfo
实体的PhoneNumber
属性需要使用Base64
进行值转换。
public class SampleDbContext : DbContext
{
public DbSet<UserInfo> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var sqlConnectionStringBuilder = new SqlConnectionStringBuilder
{
DataSource = "*******",
InitialCatalog = "ValueConverterTest",
UserID = "sa",
Password = "sa"
};
optionsBuilder.UseSqlServer(sqlConnectionStringBuilder.ConnectionString);
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserInfo>().Property(e => e.PhoneNumber).HasConversion(new Base64ValueConverter());
}
}
下面的代码是对预期的结果进行单测。
[Fact]
public async void ValueConverter_Test()
{
string phoneNumber = "13658556925";
using (SampleDbContext dbContext = new SampleDbContext())
{
await dbContext.Database.EnsureDeletedAsync();
await dbContext.Database.EnsureCreatedAsync();
dbContext.Users.Add(new UserInfo()
{
PhoneNumber = phoneNumber
});
await dbContext.SaveChangesAsync();
}
UserInfo user;
using (SampleDbContext dbContext = new SampleDbContext())
{
user = dbContext.Users.Single();
}
Assert.NotNull(user);
Assert.Equal(phoneNumber, user.PhoneNumber);
}
运行后,查询数据库中保存的结果:
手机号码 13658556925 在数据库保存的值是 MTM2NTg1NTY5MjU=。
使用值转换的另一个常用场景是将枚举的值存储为字符串类型,默认情况下,枚举的值保存到数据库中是通过整数表示的,如果需要在值存储为字符串类型。
public enum CategoryName
{
Clothing,
Footwear,
Accessories
}
public class Category
{
public int Id { get; set; }
public CategoryName Name { get; set; }
}
实体Category
的Name
属性是用枚举表示的,如果在存储时用字符串类型表示,我们可以在DbContext
的OnModelCreating
方法中使用如下代码,
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>().Property(e => e.Name).HasConversion<string>();
}
EF Core 默认提供常用类型的转换,我们只需指定存储的类型即可,框架默认支持的类型转换映射表如下:
源类型 | 目标类型 |
---|---|
enum |
int 、short 、long 、sbyte 、uint 、ushort 、ulong 、byte 、decimal 、double 、float |
bool |
int 、short 、long 、sbyte 、uint 、ushort 、ulong 、byte 、decimal 、double 、float |
bool |
string |
bool |
byte[] |
char |
string |
char |
int 、short 、long 、sbyte 、uint 、ushort 、ulong 、byte 、decimal 、double 、float |
char |
byte[] |
Guid |
byte[] |
Guid |
string |
byte[] |
string |
string |
byte[] |
DateTime 、DateTimeOffset 、TimeSpan |
string 、long 、byte[] |
int 、short 、long 、sbyte 、uint 、ushort 、ulong 、byte 、decimal 、double 、float |
string 、byte[] |
LINQ GroupBy 解析
在版本2.1之前,在EF Core中,GroupBy
表达式运算符总是在内存中进行计算的。现在支持在大多数情况下将其转换为SQL GROUP BY
子句。
var query = context.Orders
.GroupBy(o => new { o.CustomerId, o.EmployeeId })
.Select(g => new
{
g.Key.CustomerId,
g.Key.EmployeeId,
Sum = g.Sum(o => o.Amount),
Min = g.Min(o => o.Amount),
Max = g.Max(o => o.Amount),
Avg = g.Average(o => Amount)
});
相应的SQL解析如下所示:
SELECT [o].[CustomerId], [o].[EmployeeId],
SUM([o].[Amount]), MIN([o].[Amount]), MAX([o].[Amount]), AVG([o].[Amount])
FROM [Orders] AS [o]
GROUP BY [o].[CustomerId], [o].[EmployeeId];
查询类型
EF Core 模型现在可以包含查询类型。与实体类型不同,查询类型没有定义主键,也不能插入、删除或更新操作(即它们是只读的),但它们可以直接由查询返回。查询类型的一些使用场景:
- 映射到没有主键的视图
- 映射到没有主键的表
- 映射到模型中定义的查询
- 作为
FromSql()
查询的返回类型
示例,定义一个简单的Blog
和Post
模型:
public class Blog
{
public int BlogId { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public ICollection<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
}
定义一个简单的数据库视图,能够查询每博客与文章数:
db.Database.ExecuteSqlCommand(
@"CREATE VIEW View_BlogPostCounts AS
SELECT Name, Count(p.PostId) as PostCount from Blogs b
JOIN Posts p on p.BlogId = b.BlogId
GROUP BY b.Name");
定义一个类映射的数据库视图的结果:
public class BlogPostsCount
{
public string BlogName { get; set; }
public int PostCount { get; set; }
}
在DbContext
类的OnModelCreating
使用modelBuilder.Query<T>
API。 我们可以使用标准 fluent 配置 Api 来配置查询类型的映射:
public class SampleDbContext : DbContext
{
public DbQuery<BlogPostsCount> BlogPostCounts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Query<BlogPostsCount>().ToTable("View_BlogPostCounts")
.Property(v => v.BlogName).HasColumnName("Name");
}
}
查询数据库视图中的标准方式:
var postCounts = db.BlogPostCounts.ToList();
foreach (var postCount in postCounts)
{
Console.WriteLine($"{postCount.BlogName} has {postCount.PostCount} posts.");
Console.WriteLine();
}
最后
EF Core 2.1 Preview1 新增功能的部分内容已经介绍完了,希望对您有帮助。如果文章中描述的功能存在遗漏或错误,请在评论中留言,谢谢!
Entity Framework Core 2.1 Preview1 新增功能简介的更多相关文章
- Entity Framework Core 1.1 Preview 1 简介
实体框架核心(EF Core)是Entity Framework的一个轻量级,可扩展和跨平台版本. 10月25日,Entity Framework Core 1.1 Preview 1发布了. 升级到 ...
- 浅析Entity Framework Core中的并发处理
前言 Entity Framework Core 2.0更新也已经有一段时间了,园子里也有不少的文章.. 本文主要是浅析一下Entity Framework Core的并发处理方式. 1.常见的并发处 ...
- Entity Framework Core
Entity Framework是一种支持 .NET 开发人员使用 .NET 对象处理数据库的对象关系映射程序 (O/RM). 它不要求提供开发人员通常需要编写的大部分数据访问代码. Entity F ...
- [翻译] ASP.NET Core 3.0 的新增功能
ASP.NET Core 3.0 的新增功能 全文翻译自微软官方文档英文版 What's new in ASP.NET Core 3.0 本文重点介绍了 ASP.NET Core 3.0 中最重要的更 ...
- Entity Framework Core 1.1 升级通告
原文地址:https://blogs.msdn.microsoft.com/dotnet/2016/11/16/announcing-entity-framework-core-1-1/ 翻译:杨晓东 ...
- 全自动迁移数据库的实现 (Fluent NHibernate, Entity Framework Core)
在开发涉及到数据库的程序时,常会遇到一开始设计的结构不能满足需求需要再添加新字段或新表的情况,这时就需要进行数据库迁移. 实现数据库迁移有很多种办法,从手动管理各个版本的ddl脚本,到实现自己的mig ...
- Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio » 更新关系数据
Updating related data¶ 7 of 7 people found this helpful The Contoso University sample web applicatio ...
- 使用 Entity Framework Core 时,通过代码自动 Migration
一 介绍 在使用 Entity Framework Core (下面就叫 EF Core 吧)进行开发时,如果模型有变动,我们要在用 EF Core 提供的命令行工具进行手工迁移,然后再运行程序.但是 ...
- Working with Data » Getting started with ASP.NET Core and Entity Framework Core using Visual Studio »迁移
Migrations¶ 4 of 4 people found this helpful The Contoso University sample web application demonstra ...
随机推荐
- jQuery源码研究——怎么看源码
废话 这几天有想看源码的想法,于是就开始了源码的研究,经过几天的摸索发现看源码还是有点技巧在里面的,想着把这些东东写下来作为一个小总结. 在一个多月前我对Vue源码进行了一次研究,那时看源码的方式基本 ...
- qt窗口的切换
思想:在一个窗口类中声明另一继承与Qdialog的类的变量 还有在另一类中parentwidget()函数获取父类窗口,然后将其隐藏.. 窗口1: mywin1.h #ifndef MYWIN1_H ...
- shell编程之运算符(3)
declare声明变量类型 declare[+/-][选项]变量名 选项: - : 给变量设定类型属性 + : 取消变量的类型属性 -a : 将变量声明为数组型 -i : 将变量声明为整数型(inte ...
- PHP执行Session与前端JS之间的关系
<?php error_reporting(0); $path = './tmp/'; $sess_name = session_name(); echo $sess_name; $sess_i ...
- 你所有不知的margin属性
前言 致谢 本文总结于 张鑫旭老师的 CSS深入理解之margin课程,感谢张老师的辛苦付出! 难学的 CSS 作为前端狗的我们,每天都要和网页打交道.当 UI 将设计稿发给你时,CSS 的知识便显得 ...
- url路径去掉两个opencms
采用刚刚的方法安装OpenCMS之后,站点url中会存在两个opencms,造成访问url路径过长,下面讲解一种去掉两个opencms的方法. 1.去掉第一个opencms 安装时采用ROOT安装,即 ...
- Elasticsearch教程-从入门到精通(转载)
转载,原文地址:http://mageedu.blog.51cto.com/4265610/1714522?utm_source=tuicool&utm_medium=referral 各位运 ...
- SpringBoot中过滤器、监听器以及拦截器
属于javax.servlet所提供的Api 拦截器原理 简单来讲是通过动态代理实现,被访问的目标方法通过代理类(方法)来执行,这样我们就可以在真正要执行的方法执行前.后做一些处理: 通过拦截器这种方 ...
- AGC010 - B: Boxes
原题链接 题意简述 给出一个由个数构成的环,每次可以选择一个位置并从这个数起顺时针依次对每个数-1,-2,-3,-,-n.问能否将所有数全变为0. 分析 考虑一次操作对环带来了什么影响. (在后加一个 ...
- docker-compose 完整打包发布, 多服务,多节点SPRING CLOUD ,EUREKA 集群
这里不再使用 端口映射的方式,因为不同主机上,Feign 根据 docker hostname访问会有问题. 把打包的好jar copy到docker镜像里 有几个服务,就复制几个dockerfile ...