C# & EF Core 6:如何防止被包含的视图属性被更新?
我在使用EF Core的.Update()方法时,级联更新了所有关联的已包含实体,这本来符合预期,但其中一个两层嵌套的实体是映射到数据库视图的——我只需要用这个视图做查询,不想让它被更新。结果触发了以下错误:
SqlException: Update or insert of view or function '...' failed because it contains a derived or constant field
我已经通过Fluent API把对应的DbSet配置为视图(代码:ToView("...").HasKey(m => m.Id)),还尝试把所有属性的SaveBehavior设为Ignore,但还是会抛出同样的错误。
可行的解决办法
1. 手动将视图实体标记为Unchanged
.Update()会把整个对象图里的所有实体都标记为Modified,哪怕你配置了视图的SaveBehavior。所以在调用SaveChanges()前,找到嵌套的视图实体,把它的状态改成Unchanged:
// 假设你的视图实体是viewEntity context.Entry(viewEntity).State = EntityState.Unchanged; context.SaveChanges();
2. 放弃使用.Update(),手动更新主实体属性
不要直接调用.Update(主实体),而是先获取数据库中的主实体,只修改需要更新的属性。这样EF只会生成主实体的更新语句,不会处理关联的视图实体:
var existingMainEntity = context.MainEntities.Find(mainEntity.Id); // 只修改需要更新的属性 existingMainEntity.Title = mainEntity.Title; existingMainEntity.UpdateTime = DateTime.Now; context.SaveChanges();
3. 配置视图实体为完全只读(EF Core 6+推荐)
在Fluent API配置里,除了映射到视图,还可以用.IsReadOnly()直接标记实体为只读,EF会自动跳过对它的更新操作:
modelBuilder.Entity<YourViewEntity>() .ToView("YourViewName") .HasKey(m => m.Id) .IsReadOnly(); // EF Core 6及以上版本支持
如果是EF Core 5或更早版本,就逐个设置所有属性的SaveBehavior为Ignore:
var viewEntityBuilder = modelBuilder.Entity<YourViewEntity>(); viewEntityBuilder.ToView("YourViewName").HasKey(m => m.Id); foreach (var property in viewEntityBuilder.Metadata.GetProperties()) { property.SetSaveBehavior(SaveBehavior.Ignore); }
4. 查询时用AsNoTracking加载视图实体
查询主实体并包含视图实体时,加上.AsNoTracking(),让EF不跟踪视图实体的变化。更新时只Attach主实体并标记为Modified即可:
var mainEntity = context.MainEntities .Include(m => m.NestedEntity) .ThenInclude(n => n.ViewEntity) .AsNoTracking() .FirstOrDefault(m => m.Id == targetId); // 修改主实体属性后,Attach并标记为Modified context.MainEntities.Attach(mainEntity); context.Entry(mainEntity).State = EntityState.Modified; context.SaveChanges();
内容的提问来源于stack exchange,提问作者tzuleger

