探索EntityFramework Core中使用Backing Field对属性的优化
通常认为,EF Core在读写实体属性是通过属性的get/set方法实现的,这符合C#的编程规范,但是事实上并不是这么简单,现在我们定义一个简单的实体类:
/// <summary>
/// 实体;
/// </summary>
public class Entity
{
/// <summary>
/// 标识;
/// </summary>
public string? Id { get; set; }
/// <summary>
/// 名称;
/// </summary>
public string? Name{get;set;}
}
PS:ID列不用特别关注,它不是本文所关注的内容,仅是作为主键存在。
为了能够侦测到EF操作实例属性的过程,修改Name属性为普通属性,并在它的set/get方法上各添加一行输出:
private string? _name;
/// <summary>
/// 名称;
/// </summary>
public string? Name
{
get
{
Console.WriteLine("获取名称属性被调用...");
return _name;
}
set
{
Console.WriteLine("设定名称属性被调用...");
_name = value;
}
}
执行迁移并应用数据库更改后,编写以下代码以执行插入单个实体的过程:
using var context = CreateContext();
var entity = new Entity
{
Id = Guid.NewGuid().ToString(),
Name = "Janus"
};
context.Entities.Add(entity);
context.SaveChanges();
程序的输出如下:
设定名称属性被调用... |
检查数据库下该表的内容:
该记录是被成功插入了。
这里输出的一行内容自然是设定Name = "Janus"是在set名称方法中所执行的,但是EF插入记录到表中时,一定需要获取到Name的值,但是代码未执行到Name属性得set方法中即set_Name(string value).就获取到了Name的值了。
这是怎么一回事呢?
通过查阅MSDN:
https://docs.microsoft.com/en-us/ef/core/modeling/backing-field?tabs=data-annotations
得知,EF core为了提高执行速度,实现了一项针对属性的优化:即在能够通过反射寻找到属性对应的满足常见名称条件的字段(Backing field,MSDN译为支援字段)时,ef core会跳过属性的set/get相关方法,直接读取字段内容,减少调用堆栈。
也就是说,这里EF Core跳过了Name属性的set方法,直接读取了字段_name的值,同理,在设定属性时,set_Name(string value)方法同样不会被调用,在执行上述代码后,将数据插入到表中后,请执行以下代码:
using var context = CreateContext(); var entity = context.Entities.First(); Console.WriteLine($"Name = {entity.Name}");
程序的输出结果如下:
获取名称属性被调用... |
可以看到,程序获得了正确的结果,但是并未执行set_Name(string value)方法。
注意事项:
那么如果故意编写一个字段,该字段的名称满足某个属性内置字段的名称,但是实际跟该属性无关的内容呢,那么EF Core是否仍然能够正确读写属性值呢?修改Entity的名称定义如下:
private string? _name; private string? _xingming; /// <summary> /// 名称; /// </summary> public string? Name { get { Console.WriteLine("获取名称属性被调用..."); return _xingming; } set { Console.WriteLine("设定名称属性被调用..."); _xingming = value; } }
可以看到我将名称对应的字段名称修改为了_xingming,并额外添加了一个_name字段,重新执行一次获取记录的代码:
using var context = CreateContext(); var entity = context.Entities.First(); Console.WriteLine($"Name = {entity.Name}");
可以看到以下输出:
获取名称属性被调用... |
程序并未能获得记录的正确值,这是怎么一回事呢?在最后一行添加一个断点,并调试,查看entity的情况:
可以看到,字段_name获得了正确的值,Name属性因为与_name无关,所以值不正确,EF Core无法在此处判断字段和属性的关联性。当然,真实开发场景下,我们是几乎不可能这么定义实体的,这与命名规范矛盾。
若要使得EF core正确识别此处字段,可以通过Attribute注解或者FluentAPI声明属性对应的字段名称,此处,因为FluentAPI对实体是没有侵入性的,是官方推荐的,所以我仅演示FluentAPI,在Context的重写方法中OnModelCreating(ModelBuilder modelBuilder)中编写:
modelBuilder.Entity<Entity>(entity => { entity.Property(p => p.Name).HasField("_xingming"); });
重新执行上述读取值的代码,输出如下:
获取名称属性被调用... |
可以看到,值已经被正确地读取到了。