前言

通过我发表的博文可知最近一段时间会将持续讲解EntityFramework Core特性,在此之前我提到过Backing Fields,回头翻了翻感觉写的还不够好,于是乎再来讲解一番,也是自己再一次巩固,废话少说,开门见山。

EntityFramework Core Backing Fields基础

Backing Fields特性出现于EF Core 1.1,我们姑且将其翻译为返回字段,这样翻译和实际作用对应,Backing Fields允许EF Core读或者写到一个字段而非属性,说的通俗易懂一点则是允许对字段进行映射。当属性只有一个GET访问器利用此特性将非常有用,在之前版本我们必须同时需要设置GET和SET访问器,接下来我们详细来讲解Backing Fields(对字段进行映射)。

Backing Fields特性允许EF Core读或者写数据到字段中而不是属性中。默认情况下满足以下四种规则都会配置成Backing Fields。

  • _<camel-cased property name>
  • _<property name>
  • m_<camel-cased property name>
  • m_<property name>

我们首先给出本节需要用到的两个类Blog和Post,如下:

    public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime ModifiedTime { get; set; }
public ICollection<Post> Posts { get; set; }
} public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public int CommentCount { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime ModifiedTime { get; set; }
public Blog Blog { get; set; }
}

根据如上对Backing Fields的约定来我们将Blog中的Url配置成Backing Fields,如下:

    public class Blog
{ public int Id { get; set; }
public string Name { get; set; }
private string _url { get; set; }
public string Url
{
get { return _url; }
set { _url = value; }
}
public DateTime CreatedTime { get; set; }
public DateTime ModifiedTime { get; set; }
public ICollection<Post> Posts { get; set; }
}

官网对于如上配置Backing Fields即(_url)如此解释,在配置Backing Fields后,EF Core会将数据库表中数据直接写入该字段即(_url)。如果我们需要EF Core读取或写入值,那么将尽可能使用该属性。 例如,如果需要EF Core更新某个属性的值,那么它将使用属性设置器(若已定义), 如果该属性是只读的,则将它写入到字段中。想必如上解释已经很明了,无需过多再阐述。我们来演示此种情况通过字段来设置属性值,现在我们假设这样一种情况,在创建我们自己博客时,此时我们的博客Url就需要确定下来,所以在添加Blog时我们将Ur以构造函数参数传入给Backing Fields即_url,所以我们在上述基础上添加构造函数如下:

       public Blog(string url)
{
_url = url;
}

接下来我们在控制台中创建Blog并添加到数据库中,您可以先想象一下将会发生什么,如下:

            using (var context = new EFCoreDbContext())
{
context.Blogs.Add(new Blog("http://www.cnblogs/CreateMyself")
{
Name = "Jeffcky"
});
context.SaveChanges();
foreach (var blog in context.Blogs)
{
Console.WriteLine($"{blog.Id} {blog.Name} {blog.Url}");
}
}

因为EF Core默认是用无参构造函数实例化对象,既然我们自定义调用有参构造函数,所以必须显式声明无参构造函数。否则在遍历数据时将抛出异常:System.InvalidOperationException:“A parameterless constructor was not found on entity type 'Blog'. In order to create an instance of 'Blog' EF requires that a parameterless constructor be declared.”。

除了上述我们根据给出的约定EF Core将其看作为返回字段外,我们仍然可以手动利用HasField进行配置,如下:

builder.Property(p => p.Url).HasField("_url");

除此之外我们还可通过UsePropertyAccessMode方法中的参数枚举来配置对属性的访问模式,该参数枚举存在如下三种:

比如我们需要对字段访问模式为在构造函数中,那么我们可以进行如下配置:

 builder.Property(p => p.Url).HasColumnType("VARCHAR(100)").UsePropertyAccessMode(PropertyAccessMode.FieldDuringConstruction);

这里需要注意的是所有访问模式依然是通过GET或者SET访问器,比如属性设置为只读即使进行了如上配置,依然是字段。上述参数枚举说明详情请见其具体定义而定。

上述我们是将属性的字段进行映射,同时EF Core 1.1也支持不需要属性而直接映射字段,比如我们在Blog中再定义如下字段:

 public string _NonPropertyField;

接着我们进行如下映射配置,迁移后将在数据库表中生成NonPropertyBackingField列并对应字段指向_NonPropertyField。

builder.Property<string>("NonPropertyBackingField").HasField("_NonPropertyField");

EntityFramework Core Backing Fields思考

到此是不是就这么简单结束了呢?显然不是,当学习任何一门技术时,所出现技术特性是为了解决问题而不是凭空产出,什么意思呢?当我们在自学过程中看官网例子时,官网将基础知识一股脑全部灌输给我们,那我们是不是应该不假思索下,它有什么用呢?比如上述Backing Fields特性的出现,因为我给您讲解了,您就知道有这么个特性,但是不知道怎么用那和知道、了解有和区别呢?还不明白,接下来我们利用EF 6来看一个例子,通过此例子您就会顿悟了。请继续往下看。

EntityFramework 6.x 没有Backing Fields所带来问题

我们创建EF 6.x控制台程序,给出如下测试类:

    public class UseCase
{
public int Id { get; set; } private string _url { get; set; } public string Url
{
get { return _url; }
} public string GetUrl()
{
return _url = "http://www.cnblogs.com/CreateMyself";
}
}

接下来我们来添加数据看看,看看数据库表是否能正常添加:

            using (var ctx = new EfDbContext())
{
var useCases = ctx.UseCases;
var useCase = new UseCase();
useCase.GetUrl();
useCases.Add(useCase);
ctx.SaveChanges();
};

在客户端我们通过C#代码设置了Url值,但是并未同步到数据库表中,这也是EF 6.x中没有解决的问题而在EF Core利用Backing Fields轻而易举。我们能够看到当访问器GET或者SET中包含业务逻辑时这个时候就很能凸显Backing Fields的实际作用。下面我们来看看在EF Core中的实际用途。

EntityFramework Core Backing Fields用途

我们知道在EntityFramework中导航属性必须是ICollection<T>集合类型,如文章开头我们定义Blog中的Posts导航属性,我们也知道在ICollection<T>集合类型中存在Add、Remove、Clear等方法,这也就意味着有该集合类型的导航属性我们都可以对其进行添加或删除对象甚至于清除对象。正常情况下我们需要将实际业务行为代码封装在实体模型中,从这个角度出发,很显然我们不能这么做。我们希望公开一个接口,通过该接口控制业务行为以及何时进行控制,以及何时应该发生怎样的行为,这不仅仅是良好的领域驱动设计行为,也是很好的面向对象的设计行为。幸运的是在EntityFramework Core中对集合导航属性不仅仅支持ICollection<T>,同时也支持IEnumerable<T>,此时我们将Blog中的Posts集合导航属性修改成IEnemerable<Posts>,如下:

 public IEnumerable<Post> Posts { get; set; }

这个时候我们定义集合类型为IEnumerable<T>,紧接着我们修改成如下形式。

    public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime ModifiedTime { get; set; } private readonly List<Post> _posts = new List<Post>();
public IEnumerable<Post> Posts => _posts.ToList();
}

那么问题就随之而来,我们为何要修改成如上形式呢?如上定义私有的_posts返回字段并通过Posts来公开暴露,从安全角度看非常必须而且很有必要,当定义集合类型为ICollection<T>,此时我们能完全控制Post对象,也就是说能够任意进行添加、删除、清除操作。因为这完全属于内部行为,无需对外暴露。当添加Post对象时,我们在Blog对象内部定义添加方法即可。

        public void AddPost(Post post)
{
_posts.Add(post);
}

那么问题又来了,我们定义了返回字段_posts后为何传递给对外暴露的Posts时要创建副本呢?因为对外暴露的Posts最终返回的是实际List<T>集合,所以最终还是会转换成ICollection<T>集合类型,毫无疑问会造成性能的下降,所以我们需要通过创建副本来进行修正所以要ToList。还需要说明一点的是在EF Core 1.1版本中并不会映射上述私有的返回字段到数据存储中,我们需要在OnModelCreating方法中进行如下配置:

            var navigation = modelBuilder.Entity<Blog>().Metadata.FindNavigation(nameof(Blog.Posts));

            navigation.SetPropertyAccessMode(PropertyAccessMode.Field);

如上代码告诉EF Core通过命名约定发现它的字段并访问Post属性。直到EF Core 2.0仍然无法对导航属性进行返回字段配置,只能对标量属性进行返回字段配置。通过如下配置抛出异常可得知:

builder.Property(p => p.Posts).HasField("_posts");

通过github上提交的Issue得知对导航属性进行返回字段的配置会在EF Core  2.1中实现,推荐为如下配置形式:

modelBuilder.Entity<Blog>()
.HasMany(
e => e.Posts,
nb => nb.UsePropertyAccessMode(PropertyAccessMode.Field))
.WithOne(
e => e.Blog,
nb => nb.UsePropertyAccessMode(PropertyAccessMode.Field))

到此关于Backing Fields详细说明就已结束,这里我们来一个完整性总结。使用Backing Fields的时机是:大部分情况下当属性中访问器存在业务逻辑时可能会用到Backing Fields,同时对于集合导航属性 推荐使用如下组合方式。

  • 定义私有只读的返回字段(Backing Fields)。
  • 定义公共的IEnumerable<T>接口属性。
  • 对返回字段创建副本传递给对外暴露的公共接口属性

总结

侃侃而谈如上诸多理论,在实际项目中或许直接定义集合导航属性为ICollection<T>更加简单粗暴,又或者赶项目进度谁会顾及那么多呢,能实现就行。精简的内容,深入的讲解,我们下节再会。

EntityFramework Core 1.1+ Backing Fields(返回字段)的更多相关文章

  1. “幕后英雄”之Backing Fields【Microsoft Entity Framework Core随笔】

    刘德华 有一首歌叫<马桶>,其中有一句歌词是:每一个马桶都是英雄. EFCore也有一个英雄,在幕后默默地任劳任怨.它就叫 "支持字段" (Backing Fields ...

  2. 你所不知道的库存超限做法 服务器一般达到多少qps比较好[转] JAVA格物致知基础篇:你所不知道的返回码 深入了解EntityFramework Core 2.1延迟加载(Lazy Loading) EntityFramework 6.x和EntityFramework Core关系映射中导航属性必须是public? 藏在正则表达式里的陷阱 两道面试题,带你解析Java类加载机制

    你所不知道的库存超限做法 在互联网企业中,限购的做法,多种多样,有的别出心裁,有的因循守旧,但是种种做法皆想达到的目的,无外乎几种,商品卖的完,系统抗的住,库存不超限.虽然短短数语,却有着说不完,道不 ...

  3. EntityFramework Core 1.1有哪些新特性呢?我们需要知道

    前言 在项目中用到EntityFramework Core都是现学现用,及时发现问题及时测试,私下利用休闲时间也会去学习其他未曾遇到过或者用过的特性,本节我们来讲讲在EntityFramework C ...

  4. EntityFramework 7 更名为EntityFramework Core(预发布状态)

    前言 最近很少去学习和探索新的东西,尤其是之前一直比较关注的EF领域,本身不太懒,但是苦于环境比较影响自身的心情,所以迟迟没有下笔,但是不去学习感觉在精神层面缺少点什么,同时也有园友说EF又更新了,要 ...

  5. EntityFramework Core 学习笔记 —— 创建模型

    原文地址:https://docs.efproject.net/en/latest/modeling/index.html 前言: EntityFramework 使用一系列的约定来从我们的实体类细节 ...

  6. EntityFramework Core查询问题集锦(一)

    前言 和大家脱离了一段时间,有时候总想着时间挤挤总是会有的,但是并非人愿,后面会借助周末的时间来打理博客,如有问题可以在周末私信我或者加我QQ皆可,欢迎和大家一起探讨,本节我们来讨论EF Core中的 ...

  7. EntityFramework Core 2.0全局过滤(HasQueryFilter)

    前言 EntityFramework Core每一次版本的迭代和更新都会带给我们惊喜,每次都会尽量满足大部分使用者的需求.在EF Core 2.0版本中出现了全局过滤新特性即HasQueryFilte ...

  8. EntityFramework Core笔记:查询数据(3)

    1. 基本查询 1.1 加载全部数据 using System.Linq; using (var context = new LibingContext()) { var roles = contex ...

  9. EntityFramework Core问题处理集锦(一)

    前言 和大家脱离了一段时间,有时候总想着时间挤挤总是会有的,但是并非人愿,后面会借助周末的时间来打理博客,如有问题可以在周末私信我或者加我QQ皆可,欢迎和大家一起探讨,本节我们来讨论EF Core中的 ...

随机推荐

  1. Ubuntu14.04上安装Composer

    1,查看机子上有没有安装php 2,下载Composer的安装包 3,安装Composer 4,设置Composer全局可访问

  2. mongodb 3.4 分片 一主 一副 一仲 鉴权集群部署.

    Docker方式部署 为了避免过分冗余,并且在主节点挂了,还能顺利自动提升,所以加入仲裁节点 mongodb版本: 环境:一台虚拟机 三个configsvr 副本: 端口为 27020,27021,2 ...

  3. PV和UV的简单记录

    1.什么是PV值 PV(page view)即页面浏览量或点击量,是衡量一个网站或网页用户访问量.具体的说,PV值就是所有访问者在24小时(0点到24点)内看了某个网站多少个页面或某个网页多少次.PV ...

  4. MS SQL xp_instance_regwrite设置注册表疑惑

      以前写过一篇博文"MS SQL 日志记录管理",里面介绍了如何设置SQL Server的错误日志的最大归档数量,如果在SSMS的UI界面设置,可以从"Manageme ...

  5. QRCode 扫描二维码、扫描条形码、相册获取图片后识别、生成带 Logo 二维码、支持微博微信 QQ 二维码扫描样式

    目录 功能介绍 常见问题 效果图与示例 apk Gradle 依赖 布局文件 自定义属性说明 接口说明 关于我 功能介绍 根据之前公司的产品需求,参考 barcodescanner 改的,希望能帮助到 ...

  6. CBitmap的使用

    MFC提供了位图处理的基础类CBitmap,可以完成位图(bmp图像)的创建.图像数据的获取等功能.虽然功能比较少,但是在对位图进行一些简单的处理时,CBitmap类还是可以胜任的.很多人可能会采用一 ...

  7. FusionCharts 2D柱状图和折线图的组合图调试错误

    在设计FusionCharts 2D柱状图和折线图的组合图的时候,我发现不管怎么重启服务器,组合图就是不出来.后来,我通过调试发现我犯了一个致命的错误,运用平常一贯的思维,认为3D图有这种类型,那么2 ...

  8. Flex中通过RadioButton进行切换

    1.页面切换 <?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx=& ...

  9. RTSP协议分析

    RTSP 协议分析 1.概述:  RTSP(Real Time Streaming Protocol),实时流传输协议,是TCP/IP协议体系中的一个应用层协议,由哥伦比亚大学.网景和RealNetw ...

  10. 事件驱动的Java框架

    事件驱动的三个要素: 事件源:能够接收外部事件的源体. 侦听器:能够接收事件源通知的对象. 事件处理程序:用于处理事件的对象.