Entity Framework Core 2.1 Preview 1 新增功能简介
2018-03-12 06:38
543 查看
两个星期前,微软发布了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种产品错误修复之外,还包括几个常用的新功能,今天我为您详细介绍这些新功能的部分内容。
实体属性
IOC容器中注册的服务
当前的DbContext
当前实体的元数据
例如下面的实体:
其中参数与属性的配置规则如下:
参数的类型与属性的类型一致;
属性名与参数名除首字母不区分大小写之外,其它字符一致,并且可以使用
具体的匹配规则可以见Github上面的源代码:https://github.com/aspnet/EntityFrameworkCore/blob/8965f0b91cf89e36abca8636d58420cbd26c22fd/src/EFCore/Metadata/Internal/PropertyParameterBindingFactory.cs#L37-L45
不过我认识后面四种模式有待斟酌的,在.Net开发规范,应该没有人将公有的属性名使用 _、m_作为前缀。
示例代码:
其中
示例代码:
示例代码:
如果实体存在多个构造函数,框架会选择参数个数最多的那个;如果按参数个数优先选择后,依然存在多个构造函数,则会抛异常。在当前体验版本中,暂时无法直接支持自定义参数,不过在下一个发布版本中,会提供解决方案。
示例代码:
启用懒加载需要注意以下两点:
在配置中启用懒加载;
实体类不能是封闭(sealed)类,导航属性必须是虚(virtual)属性。
这种方式,在以前的博客我已经分享过,只不过当时还没有发布,原文地址:Entity Framework Core 懒加载。
示例,将插入的值进行Base64编码,在查询的时候进行Base64解码。
定义的
下面的代码是对预期的结果进行单测。
运行后,查询数据库中保存的结果:
手机号码 13658556925 在数据库保存的值是 MTM2NTg1NTY5MjU=。
使用值转换的另一个常用场景是将枚举的值存储为字符串类型,默认情况下,枚举的值保存到数据库中是通过整数表示的,如果需要在值存储为字符串类型。
实体
EF Core 默认提供常用类型的转换,我们只需指定存储的类型即可,框架默认支持的类型转换映射表如下:
相应的SQL解析如下所示:
映射到没有主键的视图
映射到没有主键的表
映射到模型中定义的查询
作为
示例,定义一个简单的
定义一个简单的数据库视图,能够查询每博客与文章数:
定义一个类映射的数据库视图的结果:
在
查询数据库视图中的标准方式:
实体构造函数参数
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 |
不过我认识后面四种模式有待斟酌的,在.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 1.1 Preview 1 简介
- 【dotnet跨平台】微软昨天宣布正式发布.NET Core RC2和.NET Core SDK Preview 1,还有Entity Framework Core RC2
- (摘)Entity Framework Core 2.1带来更好的SQL语句生成方案
- Entity Framework Core 2.1,添加种子数据
- EntityFramework Core 2.1重新梳理系列属性映射(一)
- ADO.NET Entity Framework 4.0 的新增功能
- 扩展entity framework core 实现默认字符串长度,decimal精度,entity自动注册和配置
- 使用ASP.NET Core MVC 和 Entity Framework Core 开发一个CRUD(增删改查)的应用程序
- Entity Framework Core Like 查询揭秘
- 使用EntityFrameworkCore 连接 MySql
- 解决EntityFrameworkCore “无法将“Add-Migration”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。”
- .NET Framework 3.5 版中的新增功能(3)[整理]
- 你必须知道的EntityFramework 6.x和EntityFramework Core变更追踪状态
- [UWP小白日记-11]在UWP中使用Entity Framework Core(Entity Framework 7)操作SQLite数据库(一)
- EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下?
- Entity Framework Core 创建表之间外键的删除级联效果
- EntityFramework Core进行读写分离最佳实践方式,了解一下(一)?
- EntityFramework Core 2.0微信斗牛棋牌源码 自定义标量函数两种方式
- ADO.NET Entity framework 与 LINQ TO SQL 中的功能的一些差别(一)
- [新手入门]快速学习 ADO.NET Entity Framework系列文章 #4 -- 数据新增、删除、修改(ObkectContext的 .SaveChange()方法)