EF Core 深入学习

EF Core 深入学习

EF Core操作实体属性的内部机制

  • 核心概念: EF Core 的直接字段访问

  • EF Core 在操作实体属性时,会尽量绕过属性的 getter/setter,直接操作背后的私有字段

    • 为什么要这么做?基于性能和对特殊功能支持的考虑
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ConsoleApp1
{public class Person{public long Id { get; set; }// public string Name { get; set; } // 不用简写形式,更能验证结论private string _name; // 按照结论,会直接读_name,而不会先执行Name的get和set的逻辑public string Name{get {Console.WriteLine("get被调用了!");return _name;}set {Console.WriteLine("set被调用了!");_name = value;}}}
}
  • 数据库配置和主程序代码如下
// MyDbContext.cs
using Microsoft.EntityFrameworkCore;
namespace ConsoleApp1
{public class MyDbContext : DbContext{public DbSet<Person> Persons { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){base.OnConfiguring(optionsBuilder);// 添加 TrustServerCertificate=true 解决证书问题optionsBuilder.UseSqlServer("Server=.;Database=ddd1;Trusted_Connection=True;TrustServerCertificate=true;");}}
}// Program.cs
using ConsoleApp1;Person p1 = new();
p1.Name = "yzk"; // 这里调用 setter,输出 "set被调用了!"
using var ctx = new MyDbContext();
ctx.Persons.Add(p1);
ctx.SaveChanges(); // 这里应该调用 getter,但实际没有!
  • 本次程序的运行结果
set被调用了!
  • 按照预想的结果,当执行完ctx.SaveChanges()后,控制台应该会打印get被调用了,然而并没有发生
    • 原因: EF Core 在保存时直接读取 _name 字段,没有通过 Name 属性的 getter。
using ConsoleApp1;//Person p1 = new();
//p1.Name = "yzk";
//using var ctx = new MyDbContext();
//ctx.Persons.Add(p1);
//ctx.SaveChanges();using var ctx = new MyDbContext();
Person p1 = ctx.Persons.First();
Console.WriteLine(p1.Id);
Console.WriteLine(p1.Name);  // 这里调用 getter
  • 程序运行结果
1
get被调用了!
yzk
  • 按理说,执行了两次Console,那么应该调用两次get,而事实上只调用了一次!
    • EF Core 在创建对象时直接设置 _name 字段,没有通过 setter
    • 只有当我们显式访问 p1.Name 时才调用 getter
  • 再次实验,当把字段从 _name 改为 xiaoming 后(主程序不变)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ConsoleApp1
{public class Person{public long Id { get; set; }private string xiaoming;public string Name{get {Console.WriteLine("get被调用了!");return xiaoming;}set {Console.WriteLine("set被调用了!");xiaoming = value;}}}
}
  • 运行结果如下
set被调用了!      // 创建对象时通过 setter
get被调用了!      // EF Core 内部跟踪时调用 getter  
get被调用了!      // 再次内部调用
1
get被调用了!      // 我们显式访问时调用
yzk

原理分析

EF Core 的字段发现规则

EF Core 按照以下顺序寻找匹配的字段:

  1. _<propertyName>(如 _name
  2. _<PropertyName>(如 _Name
  3. <propertyName>(如 name
  4. <PropertyName>(如 Name

为什么会这样设计?

  1. 性能优化:直接访问字段比通过属性访问器更快
  2. 绕过业务逻辑:避免属性 setter 中的验证逻辑影响数据加载
  3. 跟踪变化:EF Core 需要直接监控字段值的变化

需要避免的情况

public string Name
{get { return _name; }set { if(string.IsNullOrEmpty(value))throw new Exception("名称不能为空"); // EF Core 加载数据时会触发异常!_name = value; }
}

推荐的实践

// 如果需要在 setter 中添加逻辑,要确保不影响 EF Core 的数据加载
public string Name
{get { return _name; }set { // 避免在这里添加严格的验证逻辑_name = value; // 或者添加不阻碍数据加载的逻辑}
}

总结

  • 数据加载时:EF Core 尽量直接操作字段,不触发属性逻辑
  • 显式访问时:才会调用我们定义的 getter/setter
  • 字段命名:会影响 EF Core 的行为方式

意义: 理解这个机制对于编写正确的 EF Core 实体类和避免潜在的 bug 非常重要!