两个星期前,微软发布了EF Core 2.1 Preview 1,同时还发布了.NET Core 2.1 Preview 1ASP.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; }
}

实体CategoryName属性是用枚举表示的,如果在存储时用字符串类型表示,我们可以在DbContextOnModelCreating方法中使用如下代码,

    protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Category>().Property(e => e.Name).HasConversion<string>();
}

EF Core 默认提供常用类型的转换,我们只需指定存储的类型即可,框架默认支持的类型转换映射表如下:

源类型 目标类型
enum intshortlongsbyteuintushortulongbytedecimaldoublefloat
bool intshortlongsbyteuintushortulongbytedecimaldoublefloat
bool string
bool byte[]
char string
char intshortlongsbyteuintushortulongbytedecimaldoublefloat
char byte[]
Guid byte[]
Guid string
byte[] string
string byte[]
DateTimeDateTimeOffsetTimeSpan stringlongbyte[]
intshortlongsbyteuintushortulongbytedecimaldoublefloat stringbyte[]

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()查询的返回类型

示例,定义一个简单的BlogPost模型:

    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 新增功能简介的更多相关文章

  1. Entity Framework Core 1.1 Preview 1 简介

    实体框架核心(EF Core)是Entity Framework的一个轻量级,可扩展和跨平台版本. 10月25日,Entity Framework Core 1.1 Preview 1发布了. 升级到 ...

  2. 浅析Entity Framework Core中的并发处理

    前言 Entity Framework Core 2.0更新也已经有一段时间了,园子里也有不少的文章.. 本文主要是浅析一下Entity Framework Core的并发处理方式. 1.常见的并发处 ...

  3. Entity Framework Core

    Entity Framework是一种支持 .NET 开发人员使用 .NET 对象处理数据库的对象关系映射程序 (O/RM). 它不要求提供开发人员通常需要编写的大部分数据访问代码. Entity F ...

  4. [翻译] ASP.NET Core 3.0 的新增功能

    ASP.NET Core 3.0 的新增功能 全文翻译自微软官方文档英文版 What's new in ASP.NET Core 3.0 本文重点介绍了 ASP.NET Core 3.0 中最重要的更 ...

  5. Entity Framework Core 1.1 升级通告

    原文地址:https://blogs.msdn.microsoft.com/dotnet/2016/11/16/announcing-entity-framework-core-1-1/ 翻译:杨晓东 ...

  6. 全自动迁移数据库的实现 (Fluent NHibernate, Entity Framework Core)

    在开发涉及到数据库的程序时,常会遇到一开始设计的结构不能满足需求需要再添加新字段或新表的情况,这时就需要进行数据库迁移. 实现数据库迁移有很多种办法,从手动管理各个版本的ddl脚本,到实现自己的mig ...

  7. 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 ...

  8. 使用 Entity Framework Core 时,通过代码自动 Migration

    一 介绍 在使用 Entity Framework Core (下面就叫 EF Core 吧)进行开发时,如果模型有变动,我们要在用 EF Core 提供的命令行工具进行手工迁移,然后再运行程序.但是 ...

  9. 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 ...

随机推荐

  1. pip install 提示"no previously-included directories found matching"及"no previously-included files matching found anywhere in distribution",且偶发无法关联安装 PyPI 库的故障

    环境描述: Python 2.7.5 CentOS-7.2   报错现象: (1).在虚拟环境下运行 pip install 命令安装 PyPI 第三方库,出现类似如下告警. Running setu ...

  2. tcping-安装

    1.LINUX安装方法: 下载地址: http://www.linuxco.de/tcping/tcping.html 不过LINUX操作比WINDOWS的差.建议还是使用WINDOWS版本. 2.W ...

  3. 原生js总结(干货)

    1.js基本数据类型 number string boolean underfined null 2.查找文档中的特定元素 document.getElementById("id" ...

  4. window 下生成NodeJs(v8.9.3) 的 VS2015 解决方案node.sln

    window 下生成NodeJs(v8.9.3) 的 VS2015 解决方案node.sln 使用步骤 也可以参照 github: https://github.com/nodejs/node/blo ...

  5. map,vector 等容器内容的循环删除问题(C++)

    map,vector 等容器内容的循环删除问题(C++) map,vector等容器的循环删除不能用普通的方法删除: for(auto p=list.begin();p!=list.end();p++ ...

  6. wordpress安装五步法

    原文链接: 下载并解压缩WordPress程序安装包 在你的网页服务器上为WordPress创建一个数据库, 并且创建一个MySQL 拥有所有权限可以进入和修改的用户 重命名 wp-config-sa ...

  7. 一个简单的定时表达式(HH:mm:ss)解析

    前言 为客户开发了一个日志监控程序,监听各频道是否正常工作.其中有一个功能是这样的,当所有频道正常运行一段时间后,语音提示值班人员系统运行正常.一开始,想法比较简单,设置了一个变量,在线程不断轮询的过 ...

  8. WAMP环境搭建过程中遇到的种种问题

    1,可以选择已经打包好的继承安装包,通常包含apache,mysql,PHP,phpMyadmin.如appserv和wamp. 2,自己分别安装. 第一步:安装mysql,注意设置root对应的密码 ...

  9. UESTC - 1057 秋实大哥与花 线段树

    题意 秋实大哥是一个儒雅之人,昼听笙歌夜醉眠,若非月下即花前. 所以秋实大哥精心照料了很多花朵.现在所有的花朵排成了一行,每朵花有一个愉悦值. 秋实大哥每天要对着某一段连续的花朵歌唱,然后这些花朵的愉 ...

  10. CodeForces-748D 贪心

    这题的思维难度不是很大,属于编程实现细节处理较多的题. 暂且把每个字符串的"beauty"称为魅力值,用一个优先队列数组将同一个字符串的所有魅力值保存,通过map将不同字符串编号, ...