EF Core如何配置保存忽略子属性、用外键建关联且查询自动加载子属性?
EF Core 配置方案
你要的效果完全可以通过EF Core实体配置实现,且不需要将UnitType暴露为公共DbSet:
- 首先修正UnitType的静态实例定义,改为单例模式避免每次访问生成新实例,同时重写相等判断规则,按ID匹配实例:
public class UnitType { // 改为静态只读单例,全局唯一实例 public static UnitType Campus { get; } = new UnitType(1, nameof(Campus)); public static UnitType Building { get; } = new UnitType(2, nameof(Building)); public static UnitType Room { get; } = new UnitType(3, nameof(Room)); private UnitType(int id, string name) { Id = id; Name = name; } public int Id {get; private set;} public string Name {get;private set;} // 重写相等判断,按ID匹配,避免引用不一致导致的断言失败 public override bool Equals(object obj) { return obj is UnitType type && Id == type.Id; } public override int GetHashCode() { return HashCode.Combine(Id); } public static bool operator ==(UnitType left, UnitType right) { return EqualityComparer<UnitType>.Default.Equals(left, right); } public static bool operator !=(UnitType left, UnitType right) { return !(left == right); } }
- 在DbContext的
OnModelCreating方法中配置实体关联规则:
protected override void OnModelCreating(ModelBuilder modelBuilder) { // 配置UnitType,无需暴露为DbSet modelBuilder.Entity<UnitType>(ut => { ut.HasKey(x => x.Id); // 植入固定种子数据,EF会识别这些实例为已存在状态,不会重复插入 ut.HasData(UnitType.Campus, UnitType.Building, UnitType.Room); }); modelBuilder.Entity<Unit>(u => { // 绑定私有外键字段和导航属性 u.HasOne<UnitType>(x => x.UnitType) .WithMany() .HasForeignKey("unitTypeCode") .IsRequired(); // 配置保存时忽略UnitType导航属性的变更,无论该属性是否赋值,EF都不会尝试新增/修改关联的UnitType实体 u.Navigation(x => x.UnitType) .SetAfterSaveBehavior(PropertySaveBehavior.Ignore) .SetBeforeSaveBehavior(PropertySaveBehavior.Ignore); }); }
这套配置下,保存Unit时只会读取私有外键字段unitTypeCode的值写入数据库,完全不会处理UnitType导航属性;查询Unit时EF会自动根据外键值关联匹配对应的UnitType实例填充导航属性,完全符合你的生产环境需求。
单元测试解决方案
针对单元测试中没有EF自动填充导航属性的问题,有三种常用的轻量方案可选:
- 方案1:断言外键值(最推荐)
你单元测试的核心目标是验证Unit构造时是否正确设置了对应的UnitType,直接验证外键值即可,不需要依赖导航属性:// 先给Unit加一个只读属性暴露外键,也可用于业务逻辑判断,无侵入性 public class Unit { public Unit(UnitType unitType) { unitTypeCode = unitType.Id; } public int UnitTypeId => unitTypeCode; // 新增只读外键暴露 public UnitType UnitType {get; private set;} private int unitTypeCode; } // 测试代码直接断言外键 [TestMethod] public void Can_Set_Campus() { var unit = new Unit(UnitType.Campus); Assert.AreEqual(UnitType.Campus.Id, unit.UnitTypeId); } - 方案2:反射填充导航属性
如果必须验证导航属性的值,可以用反射给私有setter的UnitType赋值,模拟EF的行为:// 测试辅助方法 private static void FillUnitTypeNavForTest(Unit unit, UnitType unitType) { typeof(Unit) .GetProperty(nameof(Unit.UnitType), System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public) .SetValue(unit, unitType); } [TestMethod] public void Can_Set_Campus() { var unit = new Unit(UnitType.Campus); FillUnitTypeNavForTest(unit, UnitType.Campus); Assert.IsTrue(unit.UnitType == UnitType.Campus); } - 方案3:使用EF Core内存数据库
如果要完全模拟生产环境的EF行为,可以在单元测试中使用内存数据库,提前植入UnitType种子数据,插入Unit后再查询得到带导航属性的实例进行断言。
内容的提问来源于stack exchange,提问作者agarabra
相关产品推荐
相关产品推荐

