使用JsonPatchDocument更新List<string>后数据库未同步的问题
问题:JsonPatch更新List属性后EF Core未同步变更到数据库
拥有如下Music模型,其中包含List<string>类型的Types属性:
public class Music { public int Id { get; set; } public List<string> Types { get; set; } }
由于数据库不支持集合类型列,通过EF Core值转换器将Types映射为逗号分隔的字符串:
modelBuilder.Entity<Music>().HasKey(x => x.Id); modelBuilder.Entity<Music>() .Property(x => x.Types) .HasConversion( x => string.Join(',', x), x => x.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList());
编写了接收JsonPatchDocument的API端点用于更新该集合:
[HttpPatch("{id}")] public async Task<IActionResult> EditAsync([FromRoute] long id, [FromBody] JsonPatchDocument<Music> patchDoc) { var persisted = await Db.Music.FindAsync(id); if (persisted is null) return NotFound(); patchDoc.ApplyTo(persisted, ModelState); if (ModelState.TryGetValue(nameof(Music), out var modelStateEntry)) { foreach (var error in modelStateEntry.Errors) Logger.LogError(error.ErrorMessage); return BadRequest(modelStateEntry.Errors); } await Db.SaveChangesAsync(); return new ObjectResult(persisted); }
日志显示:
- 原始对象的JSON:
{ "id": 1, "types" : [ "One", "Two", "Three" ] }
- 调用
ApplyTo()后,对象的JSON已正确更新:
{ "id": 1, "types" : [ "One", "Two", "Test" ] }
但执行SaveChangesAsync()后,数据库无任何变更,日志输出:SaveChanges completed for 'OperationsDbContext' with 0 entities written to the database.
原因分析
EF Core的变更追踪机制默认仅检测引用类型的引用是否发生变化。List<string>是引用类型,当通过JsonPatch修改集合内部元素(替换、添加、删除)时,List的引用本身并未改变,EF Core会判定该属性未发生修改;再加上使用了值转换器,EF Core无法自动感知集合内部的内容变更,因此不会生成数据库更新语句。
解决方案
方法1:手动标记属性为已修改
在patchDoc.ApplyTo()之后、SaveChangesAsync()之前,手动告诉EF CoreTypes属性已变更:
patchDoc.ApplyTo(persisted, ModelState); // 新增标记属性变更的代码 Db.Entry(persisted).Property(x => x.Types).IsModified = true;
方法2:替换List实例
创建一个新的List实例替换原引用,让EF Core检测到引用变化:
patchDoc.ApplyTo(persisted, ModelState); // 新增生成新List实例的代码 persisted.Types = persisted.Types.ToList();
方法3:自定义值比较器
通过ValueComparer告诉EF Core如何比较集合内容,而非仅比较引用:
modelBuilder.Entity<Music>() .Property(x => x.Types) .HasConversion( x => string.Join(',', x), x => x.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList()) // 新增值比较器配置 .Metadata.SetValueComparer(new ValueComparer<List<string>>( (c1, c2) => c1.SequenceEqual(c2), c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), c => c.ToList()));
内容的提问来源于stack exchange,提问作者Zvi
相关产品推荐
相关产品推荐

