EF Core更新实体时如何深度复制Owned实例属性?
问题场景
在ASP.NET中使用Entity Framework Core时,将Address类标记为[Owned]作为Person实体的导航属性。调用Address的UpdateInfo方法修改其内部属性后,调用DbContext.Update无法将变更保存到数据库;但重新赋值新的Address实例却能正常保存。需求是配置DbContext,让Update能检测到Owned实体的内部修改,无需新建实例。
原因分析
EF Core的变更追踪机制中,当实体处于跟踪状态时(比如通过Find获取的实例),原本可以自动检测到Owned实体的内部属性变化。但错误调用Update方法会覆盖上下文的追踪状态:Update会将整个实体标记为Modified,但默认只会检查Owned实体的引用是否变化,不会深入检测其内部属性的修改。而重新赋值新实例时,引用发生变化,Update能识别到并保存。
解决方案
1. 移除多余的Update调用(跟踪状态场景)
如果实体是通过上下文的Find、FirstOrDefault等方法获取的(处于跟踪状态),修改Owned实体内部属性后,EF Core已经自动追踪到了变化,不需要调用Update方法,直接执行SaveChanges即可:
public void UpdatePerson(int id) { Person person = _dbContext.Persons.Find(id); person.Info.UpdateInfo("First Avenue", "New York City", "10001"); // 直接保存,无需调用Update _dbContext.SaveChanges(); }
2. 手动标记Owned实体为修改(未跟踪状态场景)
如果实体是未被上下文跟踪的(比如从客户端DTO转换而来、脱离原上下文的实例),调用Update后需要手动标记Owned实体或其属性为Modified,让EF Core识别到内部变化:
public void UpdatePerson(int id) { // 示例:获取未被跟踪的Person实例 Person person = GetUnTrackedPerson(id); person.Info.UpdateInfo("First Avenue", "New York City", "10001"); _dbContext.Persons.Update(person); // 标记整个Address实例为修改状态 var addressEntry = _dbContext.Entry(person.Info); addressEntry.State = EntityState.Modified; _dbContext.SaveChanges(); }
也可以单独标记需要修改的属性:
var addressEntry = _dbContext.Entry(person.Info); addressEntry.Property(a => a.Street).IsModified = true; addressEntry.Property(a => a.City).IsModified = true; addressEntry.Property(a => a.Zipcode).IsModified = true;
3. 让Owned实体实现INotifyPropertyChanged(自动追踪)
通过让Address实现INotifyPropertyChanged接口,EF Core会监听属性变更事件,自动检测内部属性的修改,无论实体是否被跟踪,都能正确识别需要保存的变化:
using System.ComponentModel; using System.Runtime.CompilerServices; [Microsoft.EntityFrameworkCore.Owned] public class Address : INotifyPropertyChanged { private string? _street; private string? _city; private string? _zipcode; public string? Street { get => _street; set { _street = value; OnPropertyChanged(); } } public string? City { get => _city; set { _city = value; OnPropertyChanged(); } } public string? Zipcode { get => _zipcode; set { _zipcode = value; OnPropertyChanged(); } } public void UpdateInfo(string? street, string? city, string? zipcode) { Street = street; City = city; Zipcode = zipcode; } public event PropertyChangedEventHandler? PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
这样修改后,无论调用Update还是直接修改跟踪状态的实体,EF Core都能自动检测到Address的内部属性变化并保存。
内容的提问来源于stack exchange,提问作者Maor Solomonov

