为何无法将值对象ActivitySettings的PastDaysAllowedThreshold设为0?
我为ApplicationSettings实体配置了如下Fluent API:
builder.OwnsOne( applicationSettings => applicationSettings.ActivitySettings, builder => { builder.Property(activitySettings => activitySettings.PastDaysAllowedThreshold) .HasColumnName("ActivityPastDaysAllowedThreshold") .HasDefaultValue(7); builder.HasCheckConstraint( "CK_ApplicationSettings_ActivityPastDaysAllowedThreshold", "ActivityPastDaysAllowedThreshold >= 0"); });
ActivitySettings类定义如下:
public sealed class ActivitySettings // : ValueObject { public ActivitySettings(int pastDaysAllowedThreshold) { EnsureArg.IsGte(pastDaysAllowedThreshold, 0, nameof(pastDaysAllowedThreshold)); PastDaysAllowedThreshold = pastDaysAllowedThreshold; } public int PastDaysAllowedThreshold { get; } // already tried adding a setter. }
ApplicationSettings包含以下方法:
public void SetActivitySettings(ActivitySettings activitySettings) { EnsureArg.IsNotNull(activitySettings, nameof(activitySettings)); ActivitySettings = activitySettings; RaiseDomainEvent(new ActivitySettingsUpdated(Id, activitySettings)); }
所有功能均正常,但将PastDaysAllowedThreshold设为0时,仓储跟踪的实体显示值已设为0,但数据库中并未更新,请问这是什么原因?
原因分析及解决方案
1. 缺少无参数构造函数导致EF Core变更检测异常
EF Core在加载实体或进行变更检测时,需要能够实例化ActivitySettings对象。当前你的ActivitySettings只有带参数的公开构造函数,EF Core可能无法正确初始化该对象以跟踪原始值,导致即使内存中值已变更,EF Core也识别不到需要更新数据库。
解决方法:添加一个私有无参数构造函数供EF Core使用:
public sealed class ActivitySettings // : ValueObject { // 供EF Core使用的私有无参构造函数 private ActivitySettings() { } public ActivitySettings(int pastDaysAllowedThreshold) { EnsureArg.IsGte(pastDaysAllowedThreshold, 0, nameof(pastDaysAllowedThreshold)); PastDaysAllowedThreshold = pastDaysAllowedThreshold; } public int PastDaysAllowedThreshold { get; } }
2. 值对象未重写Equals/GetHashCode导致变更判断错误
ActivitySettings作为值对象,应该基于属性值判断相等性,而非引用相等。当前EF Core会用引用比较来判断ActivitySettings是否变更,规范上值对象需要重写相等性方法,避免EF Core误判变更状态。
解决方法:重写Equals和GetHashCode方法:
public sealed class ActivitySettings // : ValueObject { private ActivitySettings() { } public ActivitySettings(int pastDaysAllowedThreshold) { EnsureArg.IsGte(pastDaysAllowedThreshold, 0, nameof(pastDaysAllowedThreshold)); PastDaysAllowedThreshold = pastDaysAllowedThreshold; } public int PastDaysAllowedThreshold { get; } public override bool Equals(object? obj) { if (obj is not ActivitySettings other) return false; return PastDaysAllowedThreshold == other.PastDaysAllowedThreshold; } public override int GetHashCode() { return PastDaysAllowedThreshold.GetHashCode(); } }
3. 验证EF Core生成的SQL语句
可以启用EF Core的日志功能,查看生成的SQL语句是否包含对ActivityPastDaysAllowedThreshold字段的更新,确认问题出在EF Core的变更检测还是数据库层面。
配置日志方法:在DbContext的OnConfiguring中添加日志输出:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information); }
运行代码后查看控制台输出的SQL,若更新语句中没有包含该字段,说明EF Core未识别到变更;若包含但数据库未更新,需检查数据库约束或事务是否提交。
内容的提问来源于stack exchange,提问作者Bassie

