探索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}");

程序的输出结果如下:

获取名称属性被调用...
Name = Janus

 

 

 

 

可以看到,程序获得了正确的结果,但是并未执行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}");

 

可以看到以下输出:

获取名称属性被调用...
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");
            });

重新执行上述读取值的代码,输出如下:

获取名称属性被调用...
Name = Janus

 

 

 

 

可以看到,值已经被正确地读取到了。

posted @ 2022-06-14 15:55  .NetDomainer  阅读(79)  评论(0编辑  收藏  举报