在Blazor应用中如何通过MVVM与EF Core仅更新已变更字段?
解决方案
方案1:复用Blazor原生EditContext变更跟踪(首推)
Blazor内置的EditContext已经自带了表单字段变更跟踪能力,不需要自行实现VM的变更跟踪逻辑,完全符合你的需求:
- 无需修改现有VM结构
- 自动记录所有用户修改过的字段
- 仅更新修改字段,不会覆盖数据库中其他字段的最新变更
- 通用逻辑可以封装为扩展方法,完全符合DRY原则
实现步骤
- 编写通用的属性赋值扩展方法,全局复用:
public static class EntityUpdateExtensions { /// <summary> /// 仅将VM中修改过的属性赋值到实体 /// </summary> public static void ApplyModifiedProperties<TEntity, TVM>(this TEntity entity, TVM viewModel, IEnumerable<string> modifiedProperties) where TEntity : class where TVM : class { var entityType = typeof(TEntity); var vmType = typeof(TVM); foreach (var propName in modifiedProperties) { var vmProperty = vmType.GetProperty(propName); var entityProperty = entityType.GetProperty(propName); // 跳过不存在/不可写的属性 if (vmProperty == null || entityProperty == null || !entityProperty.CanWrite) continue; var value = vmProperty.GetValue(viewModel); entityProperty.SetValue(entity, value); } } }
- 页面中使用EditContext绑定VM:
<EditForm EditContext="_editContext" OnValidSubmit="HandleSave"> <InputText @bind-Value="ViewModel.Name" /> <InputNumber @bind-Value="ViewModel.Price" /> <!-- 其他表单控件 --> <button type="submit">保存</button> </EditForm> @code { private ViewModel ViewModel { get; set; } private EditContext _editContext; protected override async Task OnInitializedAsync() { // 原有加载VM的逻辑不变 using var dbContext = DbContextFactory.CreateDbContext(); var entity = await dbContext.Products.FindAsync(ProductId); ViewModel = new ViewModel { Id = entity.Id, Name = entity.Name, Price = entity.Price }; // 初始化EditContext,自动跟踪变更 _editContext = new EditContext(ViewModel); } private async Task HandleSave() { // 无变更直接跳过保存 if (!_editContext.IsModified()) return; using var dbContext = DbContextFactory.CreateDbContext(); // 加载数据库最新实体 var entity = await dbContext.Products.FindAsync(ViewModel.Id); // 仅赋值修改过的字段 entity.ApplyModifiedProperties(ViewModel, _editContext.GetModifiedProperties()); await dbContext.SaveChangesAsync(); // 重置变更状态,方便后续继续编辑 _editContext.MarkAsUnmodified(); } }
复杂层级场景适配
如果存在嵌套VM、子集合这类层级结构,只需要引入支持对象图跟踪的EditContext扩展,不需要修改核心逻辑:
- 内置的
ObjectGraphDataAnnotationsValidator已经支持嵌套对象的字段变更跟踪 - 子集合的变更跟踪可以通过封装可追踪集合组件实现,核心的属性赋值逻辑无需修改
方案2:可追踪VM基类(适用于非表单编辑场景)
如果你的编辑逻辑不在EditForm中,可以封装通用的可追踪VM基类,自动收集变更字段:
public abstract class TrackableVM : INotifyPropertyChanged { private readonly HashSet<string> _modifiedProperties = new(); public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { _modifiedProperties.Add(propertyName); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } public IReadOnlySet<string> GetModifiedProperties() => _modifiedProperties.ToHashSet(); public void MarkAsUnmodified() => _modifiedProperties.Clear(); // 封装属性赋值的快捷方法,减少VM样板代码 protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer<T>.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } }
业务VM继承该基类后,属性使用SetField赋值即可自动跟踪变更,保存时和方案1一样调用扩展方法赋值即可。
进阶:并发冲突防护
如果需要避免用户修改的字段刚好被其他人覆盖,可以在实体中添加并发令牌:
public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } // 并发令牌,EF Core自动管理 [Timestamp] public byte[] RowVersion { get; set; } }
保存时捕获DbUpdateConcurrencyException,提示用户记录已被他人修改,需要刷新后重试即可。
内容的提问来源于stack exchange,提问作者Liero
相关产品推荐
相关产品推荐

